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:
@ -2,18 +2,18 @@ import { useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { CalendarEventDetails } from '@/activities/calendar/components/CalendarEventDetails';
|
import { CalendarEventDetails } from '@/activities/calendar/components/CalendarEventDetails';
|
||||||
import { FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE } from '@/activities/calendar/graphql/operation-signatures/FindOneCalendarEventOperationSignature';
|
import { FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE } from '@/activities/calendar/graphql/operation-signatures/FindOneCalendarEventOperationSignature';
|
||||||
import { viewableCalendarEventIdState } from '@/activities/calendar/states/viewableCalendarEventIdState';
|
|
||||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore';
|
import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore';
|
||||||
|
|
||||||
export const RightDrawerCalendarEvent = () => {
|
export const RightDrawerCalendarEvent = () => {
|
||||||
const { setRecords } = useSetRecordInStore();
|
const { setRecords } = useSetRecordInStore();
|
||||||
const viewableCalendarEventId = useRecoilValue(viewableCalendarEventIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
const { record: calendarEvent } = useFindOneRecord<CalendarEvent>({
|
const { record: calendarEvent } = useFindOneRecord<CalendarEvent>({
|
||||||
objectNameSingular:
|
objectNameSingular:
|
||||||
FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.objectNameSingular,
|
FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.objectNameSingular,
|
||||||
objectRecordId: viewableCalendarEventId ?? '',
|
objectRecordId: viewableRecordId ?? '',
|
||||||
recordGqlFields: FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.fields,
|
recordGqlFields: FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.fields,
|
||||||
onCompleted: (record) => setRecords([record]),
|
onCompleted: (record) => setRecords([record]),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { act, renderHook } from '@testing-library/react';
|
|||||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer';
|
import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer';
|
||||||
import { viewableCalendarEventIdState } from '@/activities/calendar/states/viewableCalendarEventIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||||
|
|
||||||
describe('useOpenCalendarEventRightDrawer', () => {
|
describe('useOpenCalendarEventRightDrawer', () => {
|
||||||
@ -10,26 +10,24 @@ describe('useOpenCalendarEventRightDrawer', () => {
|
|||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() => {
|
() => {
|
||||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||||
const viewableCalendarEventId = useRecoilValue(
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
viewableCalendarEventIdState,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...useOpenCalendarEventRightDrawer(),
|
...useOpenCalendarEventRightDrawer(),
|
||||||
isRightDrawerOpen,
|
isRightDrawerOpen,
|
||||||
viewableCalendarEventId,
|
viewableRecordId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ wrapper: RecoilRoot },
|
{ wrapper: RecoilRoot },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.isRightDrawerOpen).toBe(false);
|
expect(result.current.isRightDrawerOpen).toBe(false);
|
||||||
expect(result.current.viewableCalendarEventId).toBeNull();
|
expect(result.current.viewableRecordId).toBeNull();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.openCalendarEventRightDrawer('1234');
|
result.current.openCalendarEventRightDrawer('1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.isRightDrawerOpen).toBe(true);
|
expect(result.current.isRightDrawerOpen).toBe(true);
|
||||||
expect(result.current.viewableCalendarEventId).toBe('1234');
|
expect(result.current.viewableRecordId).toBe('1234');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { viewableCalendarEventIdState } from '@/activities/calendar/states/viewableCalendarEventIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
@ -9,14 +9,12 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
|
|||||||
export const useOpenCalendarEventRightDrawer = () => {
|
export const useOpenCalendarEventRightDrawer = () => {
|
||||||
const { openRightDrawer } = useRightDrawer();
|
const { openRightDrawer } = useRightDrawer();
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
const setViewableCalendarEventId = useSetRecoilState(
|
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||||
viewableCalendarEventIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const openCalendarEventRightDrawer = (calendarEventId: string) => {
|
const openCalendarEventRightDrawer = (calendarEventId: string) => {
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
openRightDrawer(RightDrawerPages.ViewCalendarEvent);
|
openRightDrawer(RightDrawerPages.ViewCalendarEvent);
|
||||||
setViewableCalendarEventId(calendarEventId);
|
setViewableRecordId(calendarEventId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { openCalendarEventRightDrawer };
|
return { openCalendarEventRightDrawer };
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const viewableCalendarEventIdState = createState<string | null>({
|
|
||||||
key: 'viewableCalendarEventIdState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import { ComponentDecorator } from 'twenty-ui';
|
import { ComponentDecorator } from 'twenty-ui';
|
||||||
|
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
|
||||||
import { ActivityActionBar } from '../../right-drawer/components/ActivityActionBar';
|
import { ActivityActionBar } from '../../right-drawer/components/ActivityActionBar';
|
||||||
import { Comment } from '../Comment';
|
import { Comment } from '../Comment';
|
||||||
@ -11,11 +11,11 @@ import { Comment } from '../Comment';
|
|||||||
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
||||||
|
|
||||||
const CommentSetterEffect = () => {
|
const CommentSetterEffect = () => {
|
||||||
const setViewableActivity = useSetRecoilState(viewableActivityIdState);
|
const setViewableRecord = useSetRecoilState(viewableRecordIdState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setViewableActivity('test-id');
|
setViewableRecord('test-id');
|
||||||
}, [setViewableActivity]);
|
}, [setViewableRecord]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
|
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||||
import { avatarUrl } from '~/testing/mock-data/users';
|
import { avatarUrl } from '~/testing/mock-data/users';
|
||||||
|
|
||||||
@ -13,11 +13,11 @@ import { CommentHeader } from '../CommentHeader';
|
|||||||
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
||||||
|
|
||||||
const CommentHeaderSetterEffect = () => {
|
const CommentHeaderSetterEffect = () => {
|
||||||
const setViewableActivity = useSetRecoilState(viewableActivityIdState);
|
const setViewableRecord = useSetRecoilState(viewableRecordIdState);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setViewableActivity('test-id');
|
setViewableRecord('test-id');
|
||||||
}, [setViewableActivity]);
|
}, [setViewableRecord]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { ActivityComments } from '@/activities/components/ActivityComments';
|
|||||||
import { ActivityCreationDate } from '@/activities/components/ActivityCreationDate';
|
import { ActivityCreationDate } from '@/activities/components/ActivityCreationDate';
|
||||||
import { ActivityEditorFields } from '@/activities/components/ActivityEditorFields';
|
import { ActivityEditorFields } from '@/activities/components/ActivityEditorFields';
|
||||||
import { ActivityTitleEffect } from '@/activities/components/ActivityTitleEffect';
|
import { ActivityTitleEffect } from '@/activities/components/ActivityTitleEffect';
|
||||||
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
import { ActivityTitle } from './ActivityTitle';
|
import { ActivityTitle } from './ActivityTitle';
|
||||||
@ -68,7 +67,6 @@ export const ActivityEditor = ({
|
|||||||
<StyledContainer ref={containerRef}>
|
<StyledContainer ref={containerRef}>
|
||||||
<StyledUpperPartContainer>
|
<StyledUpperPartContainer>
|
||||||
<StyledTopContainer>
|
<StyledTopContainer>
|
||||||
<ActivityTypeDropdown activityId={activityId} />
|
|
||||||
<ActivityTitleEffect activityId={activityId} />
|
<ActivityTitleEffect activityId={activityId} />
|
||||||
<StyledTitleContainer>
|
<StyledTitleContainer>
|
||||||
<ActivityTitle activityId={activityId} />
|
<ActivityTitle activityId={activityId} />
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconMail, Tag } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||||
|
|
||||||
@ -43,7 +42,6 @@ export const EmailThreadHeader = ({
|
|||||||
}: EmailThreadHeaderProps) => {
|
}: EmailThreadHeaderProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<Tag Icon={IconMail} color="gray" text="Email" onClick={() => {}} />
|
|
||||||
<StyledHead>
|
<StyledHead>
|
||||||
<StyledHeading>{subject}</StyledHeading>
|
<StyledHeading>{subject}</StyledHeading>
|
||||||
<StyledContent>
|
<StyledContent>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { act, renderHook } from '@testing-library/react';
|
|||||||
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
|
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
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';
|
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||||
|
|
||||||
const viewableEmailThreadId = '1234';
|
const viewableEmailThreadId = '1234';
|
||||||
@ -13,24 +13,22 @@ describe('useEmailThread', () => {
|
|||||||
() => {
|
() => {
|
||||||
const emailThread = useEmailThread();
|
const emailThread = useEmailThread();
|
||||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||||
const viewableEmailThreadId = useRecoilValue(
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
viewableEmailThreadIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
return { ...emailThread, isRightDrawerOpen, viewableEmailThreadId };
|
return { ...emailThread, isRightDrawerOpen, viewableRecordId };
|
||||||
},
|
},
|
||||||
{ wrapper: RecoilRoot },
|
{ wrapper: RecoilRoot },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.isRightDrawerOpen).toBe(false);
|
expect(result.current.isRightDrawerOpen).toBe(false);
|
||||||
expect(result.current.viewableEmailThreadId).toBeNull();
|
expect(result.current.viewableRecordId).toBeNull();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.openEmailThread(viewableEmailThreadId);
|
result.current.openEmailThread(viewableEmailThreadId);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.isRightDrawerOpen).toBe(true);
|
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', () => {
|
it('should close email thread if trying to open the same thread id', () => {
|
||||||
@ -40,15 +38,16 @@ describe('useEmailThread', () => {
|
|||||||
const [isRightDrawerOpen, setIsRightDrawerOpen] = useRecoilState(
|
const [isRightDrawerOpen, setIsRightDrawerOpen] = useRecoilState(
|
||||||
isRightDrawerOpenState,
|
isRightDrawerOpenState,
|
||||||
);
|
);
|
||||||
const [viewableEmailThreadId, setViewableEmailThreadId] =
|
const [viewableRecordId, setViewableRecordId] = useRecoilState(
|
||||||
useRecoilState(viewableEmailThreadIdState);
|
viewableRecordIdState,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...emailThread,
|
...emailThread,
|
||||||
isRightDrawerOpen,
|
isRightDrawerOpen,
|
||||||
viewableEmailThreadId,
|
viewableRecordId,
|
||||||
setIsRightDrawerOpen,
|
setIsRightDrawerOpen,
|
||||||
setViewableEmailThreadId,
|
setViewableRecordId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ wrapper: RecoilRoot },
|
{ wrapper: RecoilRoot },
|
||||||
@ -56,7 +55,7 @@ describe('useEmailThread', () => {
|
|||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.setIsRightDrawerOpen(true);
|
result.current.setIsRightDrawerOpen(true);
|
||||||
result.current.setViewableEmailThreadId(viewableEmailThreadId);
|
result.current.setViewableRecordId(viewableEmailThreadId);
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
@ -64,6 +63,6 @@ describe('useEmailThread', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.isRightDrawerOpen).toBe(false);
|
expect(result.current.isRightDrawerOpen).toBe(false);
|
||||||
expect(result.current.viewableEmailThreadId).toBeNull();
|
expect(result.current.viewableRecordId).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
|
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 { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||||
|
|
||||||
export const useEmailThread = () => {
|
export const useEmailThread = () => {
|
||||||
const { closeRightDrawer } = useRightDrawer();
|
const { closeRightDrawer } = useRightDrawer();
|
||||||
const openEmailThredRightDrawer = useOpenEmailThreadRightDrawer();
|
const openEmailThreadRightDrawer = useOpenEmailThreadRightDrawer();
|
||||||
|
|
||||||
const openEmailThread = useRecoilCallback(
|
const openEmailThread = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
@ -17,19 +17,19 @@ export const useEmailThread = () => {
|
|||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
const viewableEmailThreadId = snapshot
|
const viewableEmailThreadId = snapshot
|
||||||
.getLoadable(viewableEmailThreadIdState)
|
.getLoadable(viewableRecordIdState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
if (isRightDrawerOpen && viewableEmailThreadId === threadId) {
|
if (isRightDrawerOpen && viewableEmailThreadId === threadId) {
|
||||||
set(viewableEmailThreadIdState, null);
|
set(viewableRecordIdState, null);
|
||||||
closeRightDrawer();
|
closeRightDrawer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
openEmailThredRightDrawer();
|
openEmailThreadRightDrawer();
|
||||||
set(viewableEmailThreadIdState, threadId);
|
set(viewableRecordIdState, threadId);
|
||||||
},
|
},
|
||||||
[closeRightDrawer, openEmailThredRightDrawer],
|
[closeRightDrawer, openEmailThreadRightDrawer],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { openEmailThread };
|
return { openEmailThread };
|
||||||
|
|||||||
@ -4,16 +4,16 @@ import gql from 'graphql-tag';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { fetchAllThreadMessagesOperationSignatureFactory } from '@/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory';
|
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 { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
|
||||||
export const useRightDrawerEmailThread = () => {
|
export const useRightDrawerEmailThread = () => {
|
||||||
const viewableEmailThreadId = useRecoilValue(viewableEmailThreadIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
|
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
const thread = apolloClient.readFragment({
|
const thread = apolloClient.readFragment({
|
||||||
id: `TimelineThread:${viewableEmailThreadId}`,
|
id: `TimelineThread:${viewableRecordId}`,
|
||||||
fragment: gql`
|
fragment: gql`
|
||||||
fragment timelineThread on TimelineThread {
|
fragment timelineThread on TimelineThread {
|
||||||
id
|
id
|
||||||
@ -25,7 +25,7 @@ export const useRightDrawerEmailThread = () => {
|
|||||||
|
|
||||||
const FETCH_ALL_MESSAGES_OPERATION_SIGNATURE =
|
const FETCH_ALL_MESSAGES_OPERATION_SIGNATURE =
|
||||||
fetchAllThreadMessagesOperationSignatureFactory({
|
fetchAllThreadMessagesOperationSignatureFactory({
|
||||||
messageThreadId: viewableEmailThreadId,
|
messageThreadId: viewableRecordId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -39,7 +39,7 @@ export const useRightDrawerEmailThread = () => {
|
|||||||
FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.objectNameSingular,
|
FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.objectNameSingular,
|
||||||
orderBy: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.orderBy,
|
orderBy: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.orderBy,
|
||||||
recordGqlFields: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.fields,
|
recordGqlFields: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.fields,
|
||||||
skip: !viewableEmailThreadId,
|
skip: !viewableRecordId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchMoreMessages = useCallback(() => {
|
const fetchMoreMessages = useCallback(() => {
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const viewableEmailThreadIdState = createState<string | null>({
|
|
||||||
key: 'viewableEmailThreadIdState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
@ -5,7 +5,7 @@ import { RecoilRoot, useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
|
||||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
@ -18,12 +18,12 @@ describe('useOpenActivityRightDrawer', () => {
|
|||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() => {
|
() => {
|
||||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
||||||
return {
|
return {
|
||||||
openActivityRightDrawer,
|
openActivityRightDrawer,
|
||||||
activityIdInDrawer,
|
activityIdInDrawer,
|
||||||
viewableActivityId,
|
viewableRecordId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -32,11 +32,11 @@ describe('useOpenActivityRightDrawer', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.activityIdInDrawer).toBeNull();
|
expect(result.current.activityIdInDrawer).toBeNull();
|
||||||
expect(result.current.viewableActivityId).toBeNull();
|
expect(result.current.viewableRecordId).toBeNull();
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.openActivityRightDrawer('123');
|
result.current.openActivityRightDrawer('123');
|
||||||
});
|
});
|
||||||
expect(result.current.activityIdInDrawer).toBe('123');
|
expect(result.current.activityIdInDrawer).toBe('123');
|
||||||
expect(result.current.viewableActivityId).toBe('123');
|
expect(result.current.viewableRecordId).toBe('123');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
|
||||||
const mockUUID = '37873e04-2f83-4468-9ab7-3f87da6cafad';
|
const mockUUID = '37873e04-2f83-4468-9ab7-3f87da6cafad';
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ describe('useOpenCreateActivityDrawer', () => {
|
|||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() => {
|
() => {
|
||||||
const openActivityRightDrawer = useOpenCreateActivityDrawer();
|
const openActivityRightDrawer = useOpenCreateActivityDrawer();
|
||||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
||||||
const setObjectMetadataItems = useSetRecoilState(
|
const setObjectMetadataItems = useSetRecoilState(
|
||||||
objectMetadataItemsState,
|
objectMetadataItemsState,
|
||||||
@ -36,7 +36,7 @@ describe('useOpenCreateActivityDrawer', () => {
|
|||||||
return {
|
return {
|
||||||
openActivityRightDrawer,
|
openActivityRightDrawer,
|
||||||
activityIdInDrawer,
|
activityIdInDrawer,
|
||||||
viewableActivityId,
|
viewableRecordId,
|
||||||
setObjectMetadataItems,
|
setObjectMetadataItems,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -50,7 +50,7 @@ describe('useOpenCreateActivityDrawer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.activityIdInDrawer).toBeNull();
|
expect(result.current.activityIdInDrawer).toBeNull();
|
||||||
expect(result.current.viewableActivityId).toBeNull();
|
expect(result.current.viewableRecordId).toBeNull();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.openActivityRightDrawer({
|
result.current.openActivityRightDrawer({
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
@ -58,6 +58,6 @@ describe('useOpenCreateActivityDrawer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
expect(result.current.activityIdInDrawer).toBe(mockUUID);
|
expect(result.current.activityIdInDrawer).toBe(mockUUID);
|
||||||
expect(result.current.viewableActivityId).toBe(mockUUID);
|
expect(result.current.viewableRecordId).toBe(mockUUID);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
|
||||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
|
||||||
|
|
||||||
export const useOpenActivityRightDrawer = () => {
|
export const useOpenActivityRightDrawer = () => {
|
||||||
const { openRightDrawer, isRightDrawerOpen, rightDrawerPage } =
|
const { openRightDrawer, isRightDrawerOpen, rightDrawerPage } =
|
||||||
useRightDrawer();
|
useRightDrawer();
|
||||||
const [viewableActivityId, setViewableActivityId] = useRecoilState(
|
const [viewableRecordId, setViewableRecordId] = useRecoilState(
|
||||||
viewableActivityIdState,
|
viewableRecordIdState,
|
||||||
);
|
);
|
||||||
const setActivityIdInDrawer = useSetRecoilState(activityIdInDrawerState);
|
const setActivityIdInDrawer = useSetRecoilState(activityIdInDrawerState);
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
@ -21,13 +20,13 @@ export const useOpenActivityRightDrawer = () => {
|
|||||||
if (
|
if (
|
||||||
isRightDrawerOpen &&
|
isRightDrawerOpen &&
|
||||||
rightDrawerPage === RightDrawerPages.EditActivity &&
|
rightDrawerPage === RightDrawerPages.EditActivity &&
|
||||||
viewableActivityId === activityId
|
viewableRecordId === activityId
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
setViewableActivityId(activityId);
|
setViewableRecordId(activityId);
|
||||||
setActivityIdInDrawer(activityId);
|
setActivityIdInDrawer(activityId);
|
||||||
openRightDrawer(RightDrawerPages.EditActivity);
|
openRightDrawer(RightDrawerPages.EditActivity);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import { activityTargetableEntityArrayState } from '@/activities/states/activity
|
|||||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||||
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
|
||||||
import { ActivityType } from '@/activities/types/Activity';
|
import { ActivityType } from '@/activities/types/Activity';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
@ -26,7 +26,7 @@ export const useOpenCreateActivityDrawer = () => {
|
|||||||
const setActivityTargetableEntityArray = useSetRecoilState(
|
const setActivityTargetableEntityArray = useSetRecoilState(
|
||||||
activityTargetableEntityArrayState,
|
activityTargetableEntityArrayState,
|
||||||
);
|
);
|
||||||
const setViewableActivityId = useSetRecoilState(viewableActivityIdState);
|
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||||
|
|
||||||
const setIsCreatingActivity = useSetRecoilState(isActivityInCreateModeState);
|
const setIsCreatingActivity = useSetRecoilState(isActivityInCreateModeState);
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ export const useOpenCreateActivityDrawer = () => {
|
|||||||
setTemporaryActivityForEditor(createdActivityInCache);
|
setTemporaryActivityForEditor(createdActivityInCache);
|
||||||
setIsCreatingActivity(true);
|
setIsCreatingActivity(true);
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
setViewableActivityId(createdActivityInCache.id);
|
setViewableRecordId(createdActivityInCache.id);
|
||||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||||
setIsUpsertingActivityInDB(false);
|
setIsUpsertingActivityInDB(false);
|
||||||
|
|||||||
@ -1,23 +1,20 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { IconPlus, IconTrash } from 'twenty-ui';
|
import { IconTrash } from 'twenty-ui';
|
||||||
|
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
|
||||||
import { useRefreshShowPageFindManyActivitiesQueries } from '@/activities/hooks/useRefreshShowPageFindManyActivitiesQueries';
|
import { useRefreshShowPageFindManyActivitiesQueries } from '@/activities/hooks/useRefreshShowPageFindManyActivitiesQueries';
|
||||||
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
|
||||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||||
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
|
||||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
|
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useDeleteRecordFromCache } from '@/object-record/cache/hooks/useDeleteRecordFromCache';
|
import { useDeleteRecordFromCache } from '@/object-record/cache/hooks/useDeleteRecordFromCache';
|
||||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
|
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
|
||||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||||
@ -30,12 +27,9 @@ const StyledButtonContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActivityActionBar = () => {
|
export const ActivityActionBar = () => {
|
||||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
||||||
|
|
||||||
const activityTargetableEntityArray = useRecoilValue(
|
|
||||||
activityTargetableEntityArrayState,
|
|
||||||
);
|
|
||||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||||
const { deleteOneRecord: deleteOneActivity } = useDeleteOneRecord({
|
const { deleteOneRecord: deleteOneActivity } = useDeleteOneRecord({
|
||||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||||
@ -62,15 +56,9 @@ export const ActivityActionBar = () => {
|
|||||||
isUpsertingActivityInDBState,
|
isUpsertingActivityInDBState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectShowPageTargetableObject = useRecoilValue(
|
|
||||||
objectShowPageTargetableObjectState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { refreshShowPageFindManyActivitiesQueries } =
|
const { refreshShowPageFindManyActivitiesQueries } =
|
||||||
useRefreshShowPageFindManyActivitiesQueries();
|
useRefreshShowPageFindManyActivitiesQueries();
|
||||||
|
|
||||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
|
||||||
|
|
||||||
const deleteActivity = useRecoilCallback(
|
const deleteActivity = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
async () => {
|
async () => {
|
||||||
@ -86,7 +74,7 @@ export const ActivityActionBar = () => {
|
|||||||
|
|
||||||
setIsRightDrawerOpen(false);
|
setIsRightDrawerOpen(false);
|
||||||
|
|
||||||
if (!isNonEmptyString(viewableActivityId)) {
|
if (!isNonEmptyString(viewableRecordId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,13 +99,13 @@ export const ActivityActionBar = () => {
|
|||||||
await deleteManyActivityTargets(activityTargetIdsToDelete);
|
await deleteManyActivityTargets(activityTargetIdsToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteOneActivity?.(viewableActivityId);
|
await deleteOneActivity?.(viewableRecordId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
activityIdInDrawer,
|
activityIdInDrawer,
|
||||||
setIsRightDrawerOpen,
|
setIsRightDrawerOpen,
|
||||||
viewableActivityId,
|
viewableRecordId,
|
||||||
isActivityInCreateMode,
|
isActivityInCreateMode,
|
||||||
temporaryActivityForEditor,
|
temporaryActivityForEditor,
|
||||||
deleteActivityFromCache,
|
deleteActivityFromCache,
|
||||||
@ -129,34 +117,10 @@ export const ActivityActionBar = () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const record = useRecoilValue(
|
|
||||||
recordStoreFamilyState(viewableActivityId ?? ''),
|
|
||||||
);
|
|
||||||
|
|
||||||
const addActivity = () => {
|
|
||||||
setIsRightDrawerOpen(false);
|
|
||||||
if (isDefined(record) && isDefined(objectShowPageTargetableObject)) {
|
|
||||||
openCreateActivity({
|
|
||||||
type: record?.type,
|
|
||||||
customAssignee: record?.assignee,
|
|
||||||
targetableObjects: activityTargetableEntityArray,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionsAreDisabled = isUpsertingActivityInDB;
|
const actionsAreDisabled = isUpsertingActivityInDB;
|
||||||
|
|
||||||
const isCreateActionDisabled = isActivityInCreateMode;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButtonContainer>
|
<StyledButtonContainer>
|
||||||
<IconButton
|
|
||||||
Icon={IconPlus}
|
|
||||||
onClick={addActivity}
|
|
||||||
size="medium"
|
|
||||||
variant="secondary"
|
|
||||||
disabled={actionsAreDisabled || isCreateActionDisabled}
|
|
||||||
/>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
Icon={IconTrash}
|
Icon={IconTrash}
|
||||||
onClick={deleteActivity}
|
onClick={deleteActivity}
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
|
|
||||||
import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
|
|
||||||
import { RightDrawerTopBarExpandButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
|
|
||||||
import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
|
||||||
|
|
||||||
type RightDrawerActivityTopBarProps = { showActionBar?: boolean };
|
|
||||||
|
|
||||||
const StyledTopBarWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RightDrawerActivityTopBar = ({
|
|
||||||
showActionBar = true,
|
|
||||||
}: RightDrawerActivityTopBarProps) => {
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledRightDrawerTopBar>
|
|
||||||
<StyledTopBarWrapper>
|
|
||||||
<RightDrawerTopBarCloseButton />
|
|
||||||
{!isMobile && <RightDrawerTopBarExpandButton />}
|
|
||||||
</StyledTopBarWrapper>
|
|
||||||
{showActionBar && <ActivityActionBar />}
|
|
||||||
</StyledRightDrawerTopBar>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,17 +1,17 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
|
||||||
import { RightDrawerActivity } from '../RightDrawerActivity';
|
import { RightDrawerActivity } from '../RightDrawerActivity';
|
||||||
|
|
||||||
export const RightDrawerCreateActivity = () => {
|
export const RightDrawerCreateActivity = () => {
|
||||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{viewableActivityId && (
|
{viewableRecordId && (
|
||||||
<RightDrawerActivity
|
<RightDrawerActivity
|
||||||
activityId={viewableActivityId}
|
activityId={viewableRecordId}
|
||||||
showComment={false}
|
showComment={false}
|
||||||
fillTitleFromBody={true}
|
fillTitleFromBody={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
|
||||||
import { RightDrawerActivity } from '../RightDrawerActivity';
|
import { RightDrawerActivity } from '../RightDrawerActivity';
|
||||||
|
|
||||||
export const RightDrawerEditActivity = () => {
|
export const RightDrawerEditActivity = () => {
|
||||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{viewableActivityId && (
|
{viewableRecordId && (
|
||||||
<RightDrawerActivity activityId={viewableActivityId} />
|
<RightDrawerActivity activityId={viewableRecordId} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const viewableActivityIdState = createState<string | null>({
|
|
||||||
key: 'activities/viewable-activity-id',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ export const useCreateOneRecord = <
|
|||||||
skipPostOptmisticEffect = false,
|
skipPostOptmisticEffect = false,
|
||||||
}: useCreateOneRecordProps) => {
|
}: useCreateOneRecordProps) => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -50,6 +52,8 @@ export const useCreateOneRecord = <
|
|||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
|
||||||
const createOneRecord = async (input: Partial<CreatedObjectRecord>) => {
|
const createOneRecord = async (input: Partial<CreatedObjectRecord>) => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
const idForCreation = input.id ?? v4();
|
const idForCreation = input.id ?? v4();
|
||||||
|
|
||||||
const sanitizedInput = {
|
const sanitizedInput = {
|
||||||
@ -94,6 +98,7 @@ export const useCreateOneRecord = <
|
|||||||
recordsToCreate: [record],
|
recordsToCreate: [record],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
});
|
});
|
||||||
|
setLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -102,5 +107,6 @@ export const useCreateOneRecord = <
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
createOneRecord,
|
createOneRecord,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -74,6 +74,7 @@ const RelationFieldInputWithContext = ({
|
|||||||
relationObjectMetadataNameSingular:
|
relationObjectMetadataNameSingular:
|
||||||
CoreObjectNameSingular.WorkspaceMember,
|
CoreObjectNameSingular.WorkspaceMember,
|
||||||
objectMetadataNameSingular: 'person',
|
objectMetadataNameSingular: 'person',
|
||||||
|
relationFieldMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||||
|
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||||
|
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||||
|
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||||
|
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
|
|
||||||
|
export const RightDrawerRecord = () => {
|
||||||
|
const viewableRecordNameSingular = useRecoilValue(
|
||||||
|
viewableRecordNameSingularState,
|
||||||
|
);
|
||||||
|
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||||
|
|
||||||
|
if (!viewableRecordNameSingular) {
|
||||||
|
throw new Error(`Object name is not defined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!viewableRecordId) {
|
||||||
|
throw new Error(`Record id is not defined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { objectNameSingular, objectRecordId } = useRecordShowPage(
|
||||||
|
viewableRecordNameSingular ?? '',
|
||||||
|
viewableRecordId ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordFieldValueSelectorContextProvider>
|
||||||
|
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||||
|
<RecordShowContainer
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
objectRecordId={objectRecordId}
|
||||||
|
loading={false}
|
||||||
|
isInRightDrawer={true}
|
||||||
|
/>
|
||||||
|
</RecordFieldValueSelectorContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const viewableRecordIdState = createState<string | null>({
|
||||||
|
key: 'activities/viewable-record-id',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const viewableRecordNameSingularState = createState<string | null>({
|
||||||
|
key: 'activities/viewable-record-name-singular',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
@ -27,6 +27,7 @@ import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPag
|
|||||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import {
|
import {
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
FileFolder,
|
FileFolder,
|
||||||
@ -39,12 +40,14 @@ type RecordShowContainerProps = {
|
|||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
objectRecordId: string;
|
objectRecordId: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
isInRightDrawer?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordShowContainer = ({
|
export const RecordShowContainer = ({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
loading,
|
loading,
|
||||||
|
isInRightDrawer = false,
|
||||||
}: RecordShowContainerProps) => {
|
}: RecordShowContainerProps) => {
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -129,114 +132,118 @@ export const RecordShowContainer = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isReadOnly = objectMetadataItem.isRemote;
|
const isReadOnly = objectMetadataItem.isRemote;
|
||||||
|
const isMobile = useIsMobile() || isInRightDrawer;
|
||||||
const isPrefetchLoading = useIsPrefetchLoading();
|
const isPrefetchLoading = useIsPrefetchLoading();
|
||||||
|
|
||||||
return (
|
const summary = (
|
||||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
<>
|
||||||
<ShowPageContainer>
|
{isDefined(recordFromStore) && (
|
||||||
<ShowPageLeftContainer>
|
<>
|
||||||
{isDefined(recordFromStore) && (
|
<ShowPageSummaryCard
|
||||||
<>
|
id={objectRecordId}
|
||||||
<ShowPageSummaryCard
|
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
|
||||||
id={objectRecordId}
|
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
||||||
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
|
date={recordFromStore.createdAt ?? ''}
|
||||||
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
loading={isPrefetchLoading || loading || recordLoading}
|
||||||
date={recordFromStore.createdAt ?? ''}
|
title={
|
||||||
loading={isPrefetchLoading || loading || recordLoading}
|
<FieldContext.Provider
|
||||||
title={
|
value={{
|
||||||
<FieldContext.Provider
|
entityId: objectRecordId,
|
||||||
value={{
|
recoilScopeId:
|
||||||
entityId: objectRecordId,
|
objectRecordId + labelIdentifierFieldMetadataItem?.id,
|
||||||
recoilScopeId:
|
isLabelIdentifier: false,
|
||||||
objectRecordId + labelIdentifierFieldMetadataItem?.id,
|
fieldDefinition: {
|
||||||
isLabelIdentifier: false,
|
type:
|
||||||
fieldDefinition: {
|
labelIdentifierFieldMetadataItem?.type ||
|
||||||
type:
|
FieldMetadataType.Text,
|
||||||
labelIdentifierFieldMetadataItem?.type ||
|
iconName: '',
|
||||||
FieldMetadataType.Text,
|
fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '',
|
||||||
iconName: '',
|
label: labelIdentifierFieldMetadataItem?.label || '',
|
||||||
fieldMetadataId:
|
metadata: {
|
||||||
labelIdentifierFieldMetadataItem?.id ?? '',
|
fieldName: labelIdentifierFieldMetadataItem?.name || '',
|
||||||
label: labelIdentifierFieldMetadataItem?.label || '',
|
objectMetadataNameSingular: objectNameSingular,
|
||||||
metadata: {
|
},
|
||||||
fieldName:
|
defaultValue:
|
||||||
labelIdentifierFieldMetadataItem?.name || '',
|
labelIdentifierFieldMetadataItem?.defaultValue,
|
||||||
objectMetadataNameSingular: objectNameSingular,
|
},
|
||||||
},
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
defaultValue:
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
labelIdentifierFieldMetadataItem?.defaultValue,
|
}}
|
||||||
},
|
>
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
<RecordInlineCell readonly={isReadOnly} />
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
</FieldContext.Provider>
|
||||||
}}
|
}
|
||||||
>
|
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
|
||||||
<RecordInlineCell readonly={isReadOnly} />
|
onUploadPicture={
|
||||||
</FieldContext.Provider>
|
objectNameSingular === 'person' ? onUploadPicture : undefined
|
||||||
}
|
}
|
||||||
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
|
/>
|
||||||
onUploadPicture={
|
<PropertyBox>
|
||||||
objectNameSingular === 'person' ? onUploadPicture : undefined
|
{isPrefetchLoading ? (
|
||||||
}
|
<PropertyBoxSkeletonLoader />
|
||||||
/>
|
) : (
|
||||||
<PropertyBox>
|
inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
||||||
{isPrefetchLoading ? (
|
|
||||||
<PropertyBoxSkeletonLoader />
|
|
||||||
) : (
|
|
||||||
inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
|
||||||
<FieldContext.Provider
|
|
||||||
key={objectRecordId + fieldMetadataItem.id}
|
|
||||||
value={{
|
|
||||||
entityId: objectRecordId,
|
|
||||||
maxWidth: 200,
|
|
||||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
fieldDefinition:
|
|
||||||
formatFieldMetadataItemAsColumnDefinition({
|
|
||||||
field: fieldMetadataItem,
|
|
||||||
position: index,
|
|
||||||
objectMetadataItem,
|
|
||||||
showLabel: true,
|
|
||||||
labelWidth: 90,
|
|
||||||
}),
|
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordInlineCell
|
|
||||||
loading={loading || recordLoading}
|
|
||||||
readonly={isReadOnly}
|
|
||||||
/>
|
|
||||||
</FieldContext.Provider>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</PropertyBox>
|
|
||||||
<RecordDetailDuplicatesSection
|
|
||||||
objectRecordId={objectRecordId}
|
|
||||||
objectNameSingular={objectNameSingular}
|
|
||||||
/>
|
|
||||||
{relationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
key={objectRecordId + fieldMetadataItem.id}
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
value={{
|
value={{
|
||||||
entityId: objectRecordId,
|
entityId: objectRecordId,
|
||||||
|
maxWidth: 200,
|
||||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
field: fieldMetadataItem,
|
field: fieldMetadataItem,
|
||||||
position: index,
|
position: index,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
showLabel: true,
|
||||||
|
labelWidth: 90,
|
||||||
}),
|
}),
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordDetailRelationSection
|
<RecordInlineCell
|
||||||
loading={isPrefetchLoading || loading || recordLoading}
|
loading={loading || recordLoading}
|
||||||
|
readonly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
))}
|
))
|
||||||
</>
|
)}
|
||||||
)}
|
</PropertyBox>
|
||||||
|
<RecordDetailDuplicatesSection
|
||||||
|
objectRecordId={objectRecordId}
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
/>
|
||||||
|
{relationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||||
|
<FieldContext.Provider
|
||||||
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
|
value={{
|
||||||
|
entityId: objectRecordId,
|
||||||
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
|
isLabelIdentifier: false,
|
||||||
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
|
field: fieldMetadataItem,
|
||||||
|
position: index,
|
||||||
|
objectMetadataItem,
|
||||||
|
}),
|
||||||
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordDetailRelationSection
|
||||||
|
loading={isPrefetchLoading || loading || recordLoading}
|
||||||
|
/>
|
||||||
|
</FieldContext.Provider>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||||
|
<ShowPageContainer>
|
||||||
|
<ShowPageLeftContainer forceMobile={isInRightDrawer}>
|
||||||
|
{!isMobile && summary}
|
||||||
</ShowPageLeftContainer>
|
</ShowPageLeftContainer>
|
||||||
{recordFromStore ? (
|
{recordFromStore ? (
|
||||||
<ShowPageRightContainer
|
<ShowPageRightContainer
|
||||||
@ -248,6 +255,8 @@ export const RecordShowContainer = ({
|
|||||||
tasks
|
tasks
|
||||||
notes
|
notes
|
||||||
emails
|
emails
|
||||||
|
isRightDrawer={isInRightDrawer}
|
||||||
|
summary={summary}
|
||||||
loading={isPrefetchLoading || loading || recordLoading}
|
loading={isPrefetchLoading || loading || recordLoading}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { Activity } from '@/activities/types/Activity';
|
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
|
||||||
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
|
||||||
|
|
||||||
export const RecordShowContainer = ({
|
|
||||||
objectRecordId,
|
|
||||||
objectNameSingular,
|
|
||||||
}: {
|
|
||||||
objectRecordId: string;
|
|
||||||
objectNameSingular: string;
|
|
||||||
}) => {
|
|
||||||
const { record: activity, loading } = useFindOneRecord<Activity>({
|
|
||||||
objectRecordId,
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setRecordStore = useSetRecoilState(
|
|
||||||
recordStoreFamilyState(objectRecordId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const [recordLoading, setRecordLoading] = useRecoilState(
|
|
||||||
recordLoadingFamilyState(objectRecordId),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (loading !== recordLoading) {
|
|
||||||
setRecordLoading(loading);
|
|
||||||
}
|
|
||||||
}, [loading, recordLoading, setRecordLoading]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && isDefined(activity)) {
|
|
||||||
setRecordStore(activity);
|
|
||||||
}
|
|
||||||
}, [loading, setRecordStore, activity]);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||||
|
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
|
import { findOneRecordForShowPageOperationSignatureFactory } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
|
export const useRecordShowPage = (
|
||||||
|
propsObjectNameSingular: string,
|
||||||
|
propsObjectRecordId: string,
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
objectNameSingular: paramObjectNameSingular,
|
||||||
|
objectRecordId: paramObjectRecordId,
|
||||||
|
} = useParams();
|
||||||
|
|
||||||
|
const objectNameSingular = propsObjectNameSingular || paramObjectNameSingular;
|
||||||
|
const objectRecordId = propsObjectRecordId || paramObjectRecordId;
|
||||||
|
|
||||||
|
if (!objectNameSingular || !objectRecordId) {
|
||||||
|
throw new Error('Object name or Record id is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||||
|
const { labelIdentifierFieldMetadataItem } =
|
||||||
|
useLabelIdentifierFieldMetadataItem({ objectNameSingular });
|
||||||
|
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
||||||
|
const setEntityFields = useSetRecoilState(
|
||||||
|
recordStoreFamilyState(objectRecordId),
|
||||||
|
);
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const headerIcon = getIcon(objectMetadataItem?.icon);
|
||||||
|
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
|
||||||
|
findOneRecordForShowPageOperationSignatureFactory({ objectMetadataItem });
|
||||||
|
const { record, loading } = useFindOneRecord({
|
||||||
|
objectRecordId,
|
||||||
|
objectNameSingular,
|
||||||
|
recordGqlFields: FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE.fields,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDefined(record)) {
|
||||||
|
setEntityFields(record);
|
||||||
|
}
|
||||||
|
}, [record, setEntityFields]);
|
||||||
|
|
||||||
|
const correspondingFavorite = favorites.find(
|
||||||
|
(favorite) => favorite.recordId === objectRecordId,
|
||||||
|
);
|
||||||
|
const isFavorite = isDefined(correspondingFavorite);
|
||||||
|
|
||||||
|
const handleFavoriteButtonClick = async () => {
|
||||||
|
if (!objectNameSingular || !record) return;
|
||||||
|
|
||||||
|
if (isFavorite) {
|
||||||
|
deleteFavorite(correspondingFavorite.id);
|
||||||
|
} else {
|
||||||
|
createFavorite(record, objectNameSingular);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelIdentifierFieldValue =
|
||||||
|
record?.[labelIdentifierFieldMetadataItem?.name ?? ''];
|
||||||
|
const pageName =
|
||||||
|
labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FullName
|
||||||
|
? [
|
||||||
|
labelIdentifierFieldValue?.firstName,
|
||||||
|
labelIdentifierFieldValue?.lastName,
|
||||||
|
].join(' ')
|
||||||
|
: isDefined(labelIdentifierFieldValue)
|
||||||
|
? `${labelIdentifierFieldValue}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const pageTitle = pageName.trim()
|
||||||
|
? `${pageName} - ${capitalize(objectNameSingular)}`
|
||||||
|
: capitalize(objectNameSingular);
|
||||||
|
|
||||||
|
return {
|
||||||
|
objectNameSingular,
|
||||||
|
objectRecordId,
|
||||||
|
headerIcon,
|
||||||
|
loading,
|
||||||
|
pageTitle,
|
||||||
|
pageName,
|
||||||
|
isFavorite,
|
||||||
|
handleFavoriteButtonClick,
|
||||||
|
record,
|
||||||
|
objectMetadataItem,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -17,6 +17,7 @@ import { RecordDetailSectionHeader } from '@/object-record/record-show/record-de
|
|||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
|
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
|
||||||
|
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||||
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
||||||
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
||||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||||
@ -72,7 +73,7 @@ export const RecordDetailRelationSection = ({
|
|||||||
|
|
||||||
const relationRecordIds = relationRecords.map(({ id }) => id);
|
const relationRecordIds = relationRecords.map(({ id }) => id);
|
||||||
|
|
||||||
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}`;
|
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}-${entityId}`;
|
||||||
|
|
||||||
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId);
|
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId);
|
||||||
|
|
||||||
@ -138,6 +139,14 @@ export const RecordDetailRelationSection = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { createNewRecordAndOpenRightDrawer } =
|
||||||
|
useAddNewRecordAndOpenRightDrawer({
|
||||||
|
relationObjectMetadataNameSingular,
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
relationFieldMetadataItem,
|
||||||
|
entityId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordDetailSection>
|
<RecordDetailSection>
|
||||||
<RecordDetailSectionHeader
|
<RecordDetailSectionHeader
|
||||||
@ -174,6 +183,7 @@ export const RecordDetailRelationSection = ({
|
|||||||
relationObjectMetadataNameSingular
|
relationObjectMetadataNameSingular
|
||||||
}
|
}
|
||||||
relationPickerScopeId={dropdownId}
|
relationPickerScopeId={dropdownId}
|
||||||
|
onCreate={createNewRecordAndOpenRightDrawer}
|
||||||
/>
|
/>
|
||||||
</RelationPickerScope>
|
</RelationPickerScope>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export const RecordTableRow = ({
|
|||||||
getBasePathToShowPage({
|
getBasePathToShowPage({
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
}) + recordId,
|
}) + recordId,
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
isSelected: currentRowSelected,
|
isSelected: currentRowSelected,
|
||||||
isReadOnly: objectMetadataItem.isRemote ?? false,
|
isReadOnly: objectMetadataItem.isRemote ?? false,
|
||||||
isPendingRow,
|
isPendingRow,
|
||||||
|
|||||||
@ -79,6 +79,8 @@ const meta: Meta = {
|
|||||||
>
|
>
|
||||||
<RecordTableRowContext.Provider
|
<RecordTableRowContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
objectNameSingular:
|
||||||
|
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||||
recordId: mockPerformance.entityId,
|
recordId: mockPerformance.entityId,
|
||||||
rowIndex: 0,
|
rowIndex: 0,
|
||||||
pathToShowPage:
|
pathToShowPage:
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { createContext } from 'react';
|
|||||||
|
|
||||||
export type RecordTableRowContextProps = {
|
export type RecordTableRowContextProps = {
|
||||||
pathToShowPage: string;
|
pathToShowPage: string;
|
||||||
|
objectNameSingular: string;
|
||||||
recordId: string;
|
recordId: string;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
|||||||
@ -117,9 +117,21 @@ export const RecordTableCellSoftFocusMode = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
handleClick();
|
||||||
|
/*
|
||||||
|
Disabling sidepanel access for now, TODO: launch
|
||||||
|
if (!isFieldInputOnly) {
|
||||||
|
openTableCell(undefined, true);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
const isFirstColumn = columnIndex === 0;
|
const isFirstColumn = columnIndex === 0;
|
||||||
const customButtonIcon = useGetButtonIcon();
|
const customButtonIcon = useGetButtonIcon();
|
||||||
const buttonIcon = isFirstColumn ? IconArrowUpRight : customButtonIcon;
|
const buttonIcon = isFirstColumn
|
||||||
|
? IconArrowUpRight // IconLayoutSidebarRightExpand - Disabling sidepanel access for now
|
||||||
|
: customButtonIcon;
|
||||||
|
|
||||||
const showButton =
|
const showButton =
|
||||||
isDefined(buttonIcon) &&
|
isDefined(buttonIcon) &&
|
||||||
@ -136,7 +148,7 @@ export const RecordTableCellSoftFocusMode = ({
|
|||||||
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
||||||
</RecordTableCellDisplayContainer>
|
</RecordTableCellDisplayContainer>
|
||||||
{showButton && (
|
{showButton && (
|
||||||
<RecordTableCellButton onClick={handleClick} Icon={buttonIcon} />
|
<RecordTableCellButton onClick={handleButtonClick} Icon={buttonIcon} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const recordTableRow: RecordTableRowContextProps = {
|
|||||||
isSelected: false,
|
isSelected: false,
|
||||||
recordId: 'recordId',
|
recordId: 'recordId',
|
||||||
pathToShowPage: '/',
|
pathToShowPage: '/',
|
||||||
|
objectNameSingular: 'objectNameSingular',
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -31,9 +31,14 @@ export const useOpenRecordTableCellFromCell = () => {
|
|||||||
const cellPosition = useCurrentTableCellPosition();
|
const cellPosition = useCurrentTableCellPosition();
|
||||||
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
||||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
const { isReadOnly, pathToShowPage } = useContext(RecordTableRowContext);
|
const { isReadOnly, pathToShowPage, objectNameSingular } = useContext(
|
||||||
|
RecordTableRowContext,
|
||||||
|
);
|
||||||
|
|
||||||
const openTableCell = (initialValue?: string) => {
|
const openTableCell = (
|
||||||
|
initialValue?: string,
|
||||||
|
isActionButtonClick = false,
|
||||||
|
) => {
|
||||||
onOpenTableCell({
|
onOpenTableCell({
|
||||||
cellPosition,
|
cellPosition,
|
||||||
customCellHotkeyScope,
|
customCellHotkeyScope,
|
||||||
@ -41,7 +46,9 @@ export const useOpenRecordTableCellFromCell = () => {
|
|||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
pathToShowPage,
|
pathToShowPage,
|
||||||
|
objectNameSingular,
|
||||||
initialValue,
|
initialValue,
|
||||||
|
isActionButtonClick,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2';
|
import { useInitDraftValueV2 } from '@/object-record/record-field/hooks/useInitDraftValueV2';
|
||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
||||||
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
|
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
|
||||||
import { useMoveEditModeToTableCellPosition } from '@/object-record/record-table/hooks/internal/useMoveEditModeToCellPosition';
|
import { useMoveEditModeToTableCellPosition } from '@/object-record/record-table/hooks/internal/useMoveEditModeToCellPosition';
|
||||||
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
@ -28,9 +32,11 @@ export type OpenTableCellArgs = {
|
|||||||
cellPosition: TableCellPosition;
|
cellPosition: TableCellPosition;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
pathToShowPage: string;
|
pathToShowPage: string;
|
||||||
|
objectNameSingular: string;
|
||||||
customCellHotkeyScope: HotkeyScope | null;
|
customCellHotkeyScope: HotkeyScope | null;
|
||||||
fieldDefinition: FieldDefinition<FieldMetadata>;
|
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||||
entityId: string;
|
entityId: string;
|
||||||
|
isActionButtonClick: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||||
@ -48,6 +54,12 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
|
|
||||||
const initDraftValue = useInitDraftValueV2();
|
const initDraftValue = useInitDraftValueV2();
|
||||||
|
|
||||||
|
const { openRightDrawer } = useRightDrawer();
|
||||||
|
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||||
|
const setViewableRecordNameSingular = useSetRecoilState(
|
||||||
|
viewableRecordNameSingularState,
|
||||||
|
);
|
||||||
|
|
||||||
const openTableCell = useRecoilCallback(
|
const openTableCell = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
({
|
({
|
||||||
@ -55,9 +67,11 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
cellPosition,
|
cellPosition,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
pathToShowPage,
|
pathToShowPage,
|
||||||
|
objectNameSingular,
|
||||||
customCellHotkeyScope,
|
customCellHotkeyScope,
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
entityId,
|
entityId,
|
||||||
|
isActionButtonClick,
|
||||||
}: OpenTableCellArgs) => {
|
}: OpenTableCellArgs) => {
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
return;
|
return;
|
||||||
@ -78,9 +92,19 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
fieldValue,
|
fieldValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isFirstColumnCell && !isEmpty) {
|
if (isFirstColumnCell && !isEmpty && !isActionButtonClick) {
|
||||||
leaveTableFocus();
|
leaveTableFocus();
|
||||||
navigate(pathToShowPage);
|
navigate(pathToShowPage);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFirstColumnCell && !isEmpty && isActionButtonClick) {
|
||||||
|
leaveTableFocus();
|
||||||
|
setViewableRecordId(entityId);
|
||||||
|
setViewableRecordNameSingular(objectNameSingular);
|
||||||
|
openRightDrawer(RightDrawerPages.ViewRecord);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,10 +136,13 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
setDragSelectionStartEnabled,
|
setDragSelectionStartEnabled,
|
||||||
toggleClickOutsideListener,
|
toggleClickOutsideListener,
|
||||||
leaveTableFocus,
|
leaveTableFocus,
|
||||||
navigate,
|
|
||||||
setHotkeyScope,
|
setHotkeyScope,
|
||||||
initDraftValue,
|
initDraftValue,
|
||||||
moveEditModeToTableCellPosition,
|
moveEditModeToTableCellPosition,
|
||||||
|
openRightDrawer,
|
||||||
|
setViewableRecordId,
|
||||||
|
setViewableRecordNameSingular,
|
||||||
|
navigate,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { IconForbid } from 'twenty-ui';
|
import { IconForbid } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
||||||
|
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||||
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
||||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||||
|
|
||||||
@ -39,11 +42,33 @@ export const RelationPicker = ({
|
|||||||
selectedEntity: EntityForSelect | null | undefined,
|
selectedEntity: EntityForSelect | null | undefined,
|
||||||
) => onSubmit(selectedEntity ?? null);
|
) => onSubmit(selectedEntity ?? null);
|
||||||
|
|
||||||
|
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||||
|
useObjectMetadataItem({
|
||||||
|
objectNameSingular:
|
||||||
|
fieldDefinition.metadata.relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const relationFieldMetadataItem = relationObjectMetadataItem.fields.find(
|
||||||
|
({ id }) => id === fieldDefinition.metadata.relationFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { entityId } = useContext(FieldContext);
|
||||||
|
|
||||||
|
const { createNewRecordAndOpenRightDrawer } =
|
||||||
|
useAddNewRecordAndOpenRightDrawer({
|
||||||
|
relationObjectMetadataNameSingular:
|
||||||
|
fieldDefinition.metadata.relationObjectMetadataNameSingular,
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
relationFieldMetadataItem,
|
||||||
|
entityId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleEntitySelect
|
<SingleEntitySelect
|
||||||
EmptyIcon={IconForbid}
|
EmptyIcon={IconForbid}
|
||||||
emptyLabel={'No ' + fieldDefinition.label}
|
emptyLabel={'No ' + fieldDefinition.label}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
onCreate={createNewRecordAndOpenRightDrawer}
|
||||||
onEntitySelected={handleEntitySelected}
|
onEntitySelected={handleEntitySelected}
|
||||||
width={width}
|
width={width}
|
||||||
relationObjectNameSingular={
|
relationObjectNameSingular={
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export const SingleEntitySelectMenuItems = ({
|
|||||||
selectedEntity={selectedEntity}
|
selectedEntity={selectedEntity}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{showCreateButton && !loading && (
|
{showCreateButton && (
|
||||||
<>
|
<>
|
||||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||||
<CreateNewButton
|
<CreateNewButton
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { useEntitySelectSearch } from '../hooks/useEntitySelectSearch';
|
|||||||
|
|
||||||
export type SingleEntitySelectMenuItemsWithSearchProps = {
|
export type SingleEntitySelectMenuItemsWithSearchProps = {
|
||||||
excludedRelationRecordIds?: string[];
|
excludedRelationRecordIds?: string[];
|
||||||
onCreate?: () => void;
|
onCreate?: ((searchInput?: string) => void) | (() => void);
|
||||||
relationObjectNameSingular: string;
|
relationObjectNameSingular: string;
|
||||||
relationPickerScopeId?: string;
|
relationPickerScopeId?: string;
|
||||||
selectedRelationRecordIds: string[];
|
selectedRelationRecordIds: string[];
|
||||||
@ -54,8 +54,7 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
relationPickerSearchFilterState,
|
relationPickerSearchFilterState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const showCreateButton =
|
const showCreateButton = isDefined(onCreate);
|
||||||
isDefined(onCreate) && relationPickerSearchFilter !== '';
|
|
||||||
|
|
||||||
const entities = useFilteredSearchEntityQuery({
|
const entities = useFilteredSearchEntityQuery({
|
||||||
filters: [
|
filters: [
|
||||||
@ -71,6 +70,20 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
objectNameSingular: relationObjectNameSingular,
|
objectNameSingular: relationObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let onCreateWithInput = undefined;
|
||||||
|
|
||||||
|
if (isDefined(onCreate)) {
|
||||||
|
onCreateWithInput = () => {
|
||||||
|
if (onCreate.length > 0) {
|
||||||
|
(onCreate as (searchInput?: string) => void)(
|
||||||
|
relationPickerSearchFilter,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
(onCreate as () => void)();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ObjectMetadataItemsRelationPickerEffect
|
<ObjectMetadataItemsRelationPickerEffect
|
||||||
@ -81,12 +94,17 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
|||||||
<SingleEntitySelectMenuItems
|
<SingleEntitySelectMenuItems
|
||||||
entitiesToSelect={entities.entitiesToSelect}
|
entitiesToSelect={entities.entitiesToSelect}
|
||||||
loading={entities.loading}
|
loading={entities.loading}
|
||||||
selectedEntity={selectedEntity ?? entities.selectedEntities[0]}
|
selectedEntity={
|
||||||
|
selectedEntity ??
|
||||||
|
(entities.selectedEntities.length === 1
|
||||||
|
? entities.selectedEntities[0]
|
||||||
|
: undefined)
|
||||||
|
}
|
||||||
|
onCreate={onCreateWithInput}
|
||||||
{...{
|
{...{
|
||||||
EmptyIcon,
|
EmptyIcon,
|
||||||
emptyLabel,
|
emptyLabel,
|
||||||
onCancel,
|
onCancel,
|
||||||
onCreate,
|
|
||||||
onEntitySelected,
|
onEntitySelected,
|
||||||
showCreateButton,
|
showCreateButton,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -0,0 +1,115 @@
|
|||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
|
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||||
|
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||||
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
|
import {
|
||||||
|
FieldMetadataType,
|
||||||
|
RelationDefinitionType,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
type RecordDetailRelationSectionProps = {
|
||||||
|
relationObjectMetadataNameSingular: string;
|
||||||
|
relationObjectMetadataItem: ObjectMetadataItem;
|
||||||
|
relationFieldMetadataItem?: FieldMetadataItem;
|
||||||
|
entityId: string;
|
||||||
|
};
|
||||||
|
export const useAddNewRecordAndOpenRightDrawer = ({
|
||||||
|
relationObjectMetadataNameSingular,
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
relationFieldMetadataItem,
|
||||||
|
entityId,
|
||||||
|
}: RecordDetailRelationSectionProps) => {
|
||||||
|
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||||
|
const setViewableRecordNameSingular = useSetRecoilState(
|
||||||
|
viewableRecordNameSingularState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { createOneRecord } = useCreateOneRecord({
|
||||||
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { updateOneRecord } = useUpdateOneRecord({
|
||||||
|
objectNameSingular:
|
||||||
|
relationFieldMetadataItem?.relationDefinition?.targetObjectMetadata
|
||||||
|
.nameSingular ?? 'workspaceMember',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { openRightDrawer } = useRightDrawer();
|
||||||
|
|
||||||
|
if (
|
||||||
|
relationObjectMetadataNameSingular === 'workspaceMember' ||
|
||||||
|
!isDefined(
|
||||||
|
relationFieldMetadataItem?.relationDefinition?.targetObjectMetadata
|
||||||
|
.nameSingular,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
createNewRecordAndOpenRightDrawer: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createNewRecordAndOpenRightDrawer: async (searchInput?: string) => {
|
||||||
|
const newRecordId = v4();
|
||||||
|
const labelIdentifierType = getLabelIdentifierFieldMetadataItem(
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
)?.type;
|
||||||
|
const createRecordPayload: {
|
||||||
|
id: string;
|
||||||
|
name:
|
||||||
|
| string
|
||||||
|
| { firstName: string | undefined; lastName: string | undefined };
|
||||||
|
[key: string]: any;
|
||||||
|
} =
|
||||||
|
labelIdentifierType === FieldMetadataType.FullName
|
||||||
|
? {
|
||||||
|
id: newRecordId,
|
||||||
|
name:
|
||||||
|
searchInput && searchInput.split(' ').length > 1
|
||||||
|
? {
|
||||||
|
firstName: searchInput.split(' ')[0],
|
||||||
|
lastName: searchInput.split(' ').slice(1).join(' '),
|
||||||
|
}
|
||||||
|
: { firstName: searchInput, lastName: '' },
|
||||||
|
}
|
||||||
|
: { id: newRecordId, name: searchInput ?? '' };
|
||||||
|
|
||||||
|
if (
|
||||||
|
relationFieldMetadataItem?.relationDefinition?.direction ===
|
||||||
|
RelationDefinitionType.ManyToOne
|
||||||
|
) {
|
||||||
|
createRecordPayload[
|
||||||
|
`${relationFieldMetadataItem?.relationDefinition?.targetFieldMetadata.name}Id`
|
||||||
|
] = entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOneRecord(createRecordPayload);
|
||||||
|
|
||||||
|
if (
|
||||||
|
relationFieldMetadataItem?.relationDefinition?.direction ===
|
||||||
|
RelationDefinitionType.OneToMany
|
||||||
|
) {
|
||||||
|
await updateOneRecord({
|
||||||
|
idToUpdate: entityId,
|
||||||
|
updateOneRecordInput: {
|
||||||
|
[`${relationFieldMetadataItem?.relationDefinition?.targetFieldMetadata.name}Id`]:
|
||||||
|
newRecordId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setViewableRecordId(newRecordId);
|
||||||
|
setViewableRecordNameSingular(relationObjectMetadataNameSingular);
|
||||||
|
openRightDrawer(RightDrawerPages.ViewRecord);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1 +1,50 @@
|
|||||||
export { RightDrawerContainer as PageBody } from './RightDrawerContainer';
|
import { ReactNode } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
|
||||||
|
|
||||||
|
import { PagePanel } from './PagePanel';
|
||||||
|
|
||||||
|
type PageBodyProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMainContainer = styled.div`
|
||||||
|
background: ${({ theme }) => theme.background.noisy};
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
min-height: 0;
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||||
|
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||||
|
padding-left: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(3)};
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type LeftContainerProps = {
|
||||||
|
isRightDrawerOpen?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledLeftContainer = styled.div<LeftContainerProps>`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PageBody = ({ children }: PageBodyProps) => (
|
||||||
|
<StyledMainContainer>
|
||||||
|
<StyledLeftContainer>
|
||||||
|
<PagePanel>{children}</PagePanel>
|
||||||
|
</StyledLeftContainer>
|
||||||
|
<RightDrawer />
|
||||||
|
</StyledMainContainer>
|
||||||
|
);
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import { IconComponent } from 'twenty-ui';
|
|||||||
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
|
import { PageBody } from './PageBody';
|
||||||
import { PageHeader } from './PageHeader';
|
import { PageHeader } from './PageHeader';
|
||||||
import { RightDrawerContainer } from './RightDrawerContainer';
|
|
||||||
|
|
||||||
type SubMenuTopBarContainerProps = {
|
type SubMenuTopBarContainerProps = {
|
||||||
children: JSX.Element | JSX.Element[];
|
children: JSX.Element | JSX.Element[];
|
||||||
@ -32,7 +32,7 @@ export const SubMenuTopBarContainer = ({
|
|||||||
return (
|
return (
|
||||||
<StyledContainer isMobile={isMobile} className={className}>
|
<StyledContainer isMobile={isMobile} className={className}>
|
||||||
{isMobile && <PageHeader title={title} Icon={Icon} />}
|
{isMobile && <PageHeader title={title} Icon={Icon} />}
|
||||||
<RightDrawerContainer>{children}</RightDrawerContainer>
|
<PageBody>{children}</PageBody>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Key } from 'ts-key-enum';
|
|||||||
|
|
||||||
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 { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompleted';
|
import { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompleted';
|
||||||
|
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
@ -46,6 +47,8 @@ export const RightDrawer = () => {
|
|||||||
isRightDrawerOpenState,
|
isRightDrawerOpenState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||||
|
|
||||||
const isRightDrawerExpanded = useRecoilValue(isRightDrawerExpandedState);
|
const isRightDrawerExpanded = useRecoilValue(isRightDrawerExpandedState);
|
||||||
const [, setIsRightDrawerAnimationCompleted] = useRecoilState(
|
const [, setIsRightDrawerAnimationCompleted] = useRecoilState(
|
||||||
isRightDrawerAnimationCompletedState,
|
isRightDrawerAnimationCompletedState,
|
||||||
@ -69,8 +72,11 @@ export const RightDrawer = () => {
|
|||||||
const isRightDrawerOpen = snapshot
|
const isRightDrawerOpen = snapshot
|
||||||
.getLoadable(isRightDrawerOpenState)
|
.getLoadable(isRightDrawerOpenState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
const isRightDrawerMinimized = snapshot
|
||||||
|
.getLoadable(isRightDrawerMinimizedState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
if (isRightDrawerOpen) {
|
if (isRightDrawerOpen && !isRightDrawerMinimized) {
|
||||||
set(rightDrawerCloseEventState, event);
|
set(rightDrawerCloseEventState, event);
|
||||||
closeRightDrawer();
|
closeRightDrawer();
|
||||||
}
|
}
|
||||||
@ -115,6 +121,13 @@ export const RightDrawer = () => {
|
|||||||
closed: {
|
closed: {
|
||||||
x: '100%',
|
x: '100%',
|
||||||
},
|
},
|
||||||
|
minimized: {
|
||||||
|
x: '0%',
|
||||||
|
width: 'auto',
|
||||||
|
height: 'auto',
|
||||||
|
bottom: '0',
|
||||||
|
top: 'auto',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const handleAnimationComplete = () => {
|
const handleAnimationComplete = () => {
|
||||||
setIsRightDrawerAnimationCompleted(isRightDrawerOpen);
|
setIsRightDrawerAnimationCompleted(isRightDrawerOpen);
|
||||||
@ -122,8 +135,20 @@ export const RightDrawer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
initial="closed"
|
initial={
|
||||||
animate={isRightDrawerOpen ? 'normal' : 'closed'}
|
isRightDrawerOpen
|
||||||
|
? isRightDrawerMinimized
|
||||||
|
? 'minimized'
|
||||||
|
: 'normal'
|
||||||
|
: 'closed'
|
||||||
|
}
|
||||||
|
animate={
|
||||||
|
isRightDrawerOpen
|
||||||
|
? isRightDrawerMinimized
|
||||||
|
? 'minimized'
|
||||||
|
: 'normal'
|
||||||
|
: 'closed'
|
||||||
|
}
|
||||||
variants={variants}
|
variants={variants}
|
||||||
transition={{
|
transition={{
|
||||||
duration: theme.animation.duration.normal,
|
duration: theme.animation.duration.normal,
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent';
|
import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent';
|
||||||
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
|
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
|
||||||
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
||||||
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
||||||
|
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
|
||||||
|
import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar';
|
||||||
|
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||||
|
|
||||||
import { RightDrawerActivityTopBar } from '../../../../activities/right-drawer/components/RightDrawerActivityTopBar';
|
|
||||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||||
|
|
||||||
@ -30,19 +32,23 @@ const StyledRightDrawerBody = styled.div`
|
|||||||
const RIGHT_DRAWER_PAGES_CONFIG = {
|
const RIGHT_DRAWER_PAGES_CONFIG = {
|
||||||
[RightDrawerPages.CreateActivity]: {
|
[RightDrawerPages.CreateActivity]: {
|
||||||
page: <RightDrawerCreateActivity />,
|
page: <RightDrawerCreateActivity />,
|
||||||
topBar: <RightDrawerActivityTopBar />,
|
topBar: <RightDrawerTopBar page={RightDrawerPages.CreateActivity} />,
|
||||||
},
|
},
|
||||||
[RightDrawerPages.EditActivity]: {
|
[RightDrawerPages.EditActivity]: {
|
||||||
page: <RightDrawerEditActivity />,
|
page: <RightDrawerEditActivity />,
|
||||||
topBar: <RightDrawerActivityTopBar />,
|
topBar: <RightDrawerTopBar page={RightDrawerPages.EditActivity} />,
|
||||||
},
|
},
|
||||||
[RightDrawerPages.ViewEmailThread]: {
|
[RightDrawerPages.ViewEmailThread]: {
|
||||||
page: <RightDrawerEmailThread />,
|
page: <RightDrawerEmailThread />,
|
||||||
topBar: <RightDrawerActivityTopBar showActionBar={false} />,
|
topBar: <RightDrawerTopBar page={RightDrawerPages.ViewEmailThread} />,
|
||||||
},
|
},
|
||||||
[RightDrawerPages.ViewCalendarEvent]: {
|
[RightDrawerPages.ViewCalendarEvent]: {
|
||||||
page: <RightDrawerCalendarEvent />,
|
page: <RightDrawerCalendarEvent />,
|
||||||
topBar: <RightDrawerActivityTopBar showActionBar={false} />,
|
topBar: <RightDrawerTopBar page={RightDrawerPages.ViewCalendarEvent} />,
|
||||||
|
},
|
||||||
|
[RightDrawerPages.ViewRecord]: {
|
||||||
|
page: <RightDrawerRecord />,
|
||||||
|
topBar: <RightDrawerTopBar page={RightDrawerPages.ViewRecord} />,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,10 +59,14 @@ export const RightDrawerRouter = () => {
|
|||||||
? RIGHT_DRAWER_PAGES_CONFIG[rightDrawerPage]
|
? RIGHT_DRAWER_PAGES_CONFIG[rightDrawerPage]
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRightDrawerPage>
|
<StyledRightDrawerPage>
|
||||||
{topBar}
|
{topBar}
|
||||||
<StyledRightDrawerBody>{page}</StyledRightDrawerBody>
|
{!isRightDrawerMinimized && (
|
||||||
|
<StyledRightDrawerBody>{page}</StyledRightDrawerBody>
|
||||||
|
)}
|
||||||
</StyledRightDrawerPage>
|
</StyledRightDrawerPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
import { Chip, ChipAccent, ChipSize, useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||||
|
import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
|
||||||
|
import { RightDrawerTopBarExpandButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
|
||||||
|
import { RightDrawerTopBarMinimizeButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarMinimizeButton';
|
||||||
|
import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar';
|
||||||
|
import { RIGHT_DRAWER_PAGE_ICONS } from '@/ui/layout/right-drawer/constants/RightDrawerPageIcons';
|
||||||
|
import { RIGHT_DRAWER_PAGE_TITLES } from '@/ui/layout/right-drawer/constants/RightDrawerPageTitles';
|
||||||
|
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||||
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
|
const StyledTopBarWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMinimizeTopBarTitleContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
height: 24px;
|
||||||
|
width: 168px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMinimizeTopBarTitle = styled.div`
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMinimizeTopBarIcon = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RightDrawerTopBar = ({ page }: { page: RightDrawerPages }) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const [isRightDrawerMinimized, setIsRightDrawerMinimized] = useRecoilState(
|
||||||
|
isRightDrawerMinimizedState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const handleOnclick = () => {
|
||||||
|
if (isRightDrawerMinimized) {
|
||||||
|
setIsRightDrawerMinimized(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
|
const PageIcon = getIcon(RIGHT_DRAWER_PAGE_ICONS[page]);
|
||||||
|
|
||||||
|
const viewableRecordNameSingular = useRecoilValue(
|
||||||
|
viewableRecordNameSingularState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular: viewableRecordNameSingular ?? 'company',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ObjectIcon = getIcon(objectMetadataItem.icon);
|
||||||
|
|
||||||
|
const label =
|
||||||
|
page === RightDrawerPages.ViewRecord
|
||||||
|
? objectMetadataItem.labelSingular
|
||||||
|
: RIGHT_DRAWER_PAGE_TITLES[page];
|
||||||
|
|
||||||
|
const Icon = page === RightDrawerPages.ViewRecord ? ObjectIcon : PageIcon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledRightDrawerTopBar
|
||||||
|
onClick={handleOnclick}
|
||||||
|
isRightDrawerMinimized={isRightDrawerMinimized}
|
||||||
|
>
|
||||||
|
{!isRightDrawerMinimized &&
|
||||||
|
(page === RightDrawerPages.EditActivity ||
|
||||||
|
page === RightDrawerPages.CreateActivity) && <ActivityActionBar />}
|
||||||
|
{!isRightDrawerMinimized &&
|
||||||
|
page !== RightDrawerPages.EditActivity &&
|
||||||
|
page !== RightDrawerPages.CreateActivity && (
|
||||||
|
<Chip
|
||||||
|
label={label}
|
||||||
|
leftComponent={<Icon size={theme.icon.size.md} />}
|
||||||
|
size={ChipSize.Large}
|
||||||
|
accent={ChipAccent.TextSecondary}
|
||||||
|
clickable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isRightDrawerMinimized && (
|
||||||
|
<StyledMinimizeTopBarTitleContainer>
|
||||||
|
<StyledMinimizeTopBarIcon>
|
||||||
|
<Icon size={theme.icon.size.md} />
|
||||||
|
</StyledMinimizeTopBarIcon>
|
||||||
|
<StyledMinimizeTopBarTitle>{label}</StyledMinimizeTopBarTitle>
|
||||||
|
</StyledMinimizeTopBarTitleContainer>
|
||||||
|
)}
|
||||||
|
<StyledTopBarWrapper>
|
||||||
|
{!isMobile && !isRightDrawerMinimized && (
|
||||||
|
<RightDrawerTopBarMinimizeButton />
|
||||||
|
)}
|
||||||
|
{!isMobile && !isRightDrawerMinimized && (
|
||||||
|
<RightDrawerTopBarExpandButton />
|
||||||
|
)}
|
||||||
|
<RightDrawerTopBarCloseButton />
|
||||||
|
</StyledTopBarWrapper>
|
||||||
|
</StyledRightDrawerTopBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { IconChevronsRight } from 'twenty-ui';
|
import { IconX } from 'twenty-ui';
|
||||||
|
|
||||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ export const RightDrawerTopBarCloseButton = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LightIconButton
|
<LightIconButton
|
||||||
Icon={IconChevronsRight}
|
Icon={IconX}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
size="medium"
|
size="medium"
|
||||||
accent="tertiary"
|
accent="tertiary"
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import {
|
import {
|
||||||
IconLayoutSidebarRightCollapse,
|
IconLayoutSidebarRightCollapse,
|
||||||
IconLayoutSidebarRightExpand,
|
IconLayoutSidebarRightExpand,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
|
||||||
|
|
||||||
export const RightDrawerTopBarExpandButton = () => {
|
export const RightDrawerTopBarExpandButton = () => {
|
||||||
const [isRightDrawerExpanded, setIsRightDrawerExpanded] = useRecoilState(
|
const { isRightDrawerExpanded, downsizeRightDrawer, expandRightDrawer } =
|
||||||
isRightDrawerExpandedState,
|
useRightDrawer();
|
||||||
);
|
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
setIsRightDrawerExpanded(!isRightDrawerExpanded);
|
if (isRightDrawerExpanded === true) {
|
||||||
|
downsizeRightDrawer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expandRightDrawer();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { IconMinus } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
|
|
||||||
|
export const RightDrawerTopBarMinimizeButton = () => {
|
||||||
|
const { isRightDrawerMinimized, minimizeRightDrawer, maximizeRightDrawer } =
|
||||||
|
useRightDrawer();
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
isRightDrawerMinimized ? maximizeRightDrawer() : minimizeRightDrawer();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LightIconButton
|
||||||
|
Icon={IconMinus}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
size="medium"
|
||||||
|
accent="tertiary"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const StyledRightDrawerTopBar = styled.div`
|
export const StyledRightDrawerTopBar = styled.div<{
|
||||||
|
isRightDrawerMinimized: boolean;
|
||||||
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
@ -9,9 +11,12 @@ export const StyledRightDrawerTopBar = styled.div`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
height: 56px;
|
height: ${({ isRightDrawerMinimized }) =>
|
||||||
|
isRightDrawerMinimized ? '40px' : '56px'};
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||||
|
cursor: ${({ isRightDrawerMinimized }) =>
|
||||||
|
isRightDrawerMinimized ? 'pointer' : 'default'};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
import { RightDrawerActivityTopBar } from '../RightDrawerActivityTopBar';
|
import { RightDrawerTopBar } from '../RightDrawerTopBar';
|
||||||
|
|
||||||
const meta: Meta<typeof RightDrawerActivityTopBar> = {
|
const meta: Meta<typeof RightDrawerTopBar> = {
|
||||||
title: 'Modules/Activities/RightDrawer/RightDrawerActivityTopBar',
|
title: 'Modules/Activities/RightDrawer/RightDrawerActivityTopBar',
|
||||||
component: RightDrawerActivityTopBar,
|
component: RightDrawerTopBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<div style={{ width: '500px' }}>
|
<div style={{ width: '500px' }}>
|
||||||
@ -22,6 +22,6 @@ const meta: Meta<typeof RightDrawerActivityTopBar> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof RightDrawerActivityTopBar>;
|
type Story = StoryObj<typeof RightDrawerTopBar>;
|
||||||
|
|
||||||
export const Default: Story = {};
|
export const Default: Story = {};
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
|
|
||||||
|
export const RIGHT_DRAWER_PAGE_ICONS = {
|
||||||
|
[RightDrawerPages.CreateActivity]: 'IconNote',
|
||||||
|
[RightDrawerPages.EditActivity]: 'IconNote',
|
||||||
|
[RightDrawerPages.ViewEmailThread]: 'IconMail',
|
||||||
|
[RightDrawerPages.ViewCalendarEvent]: 'IconCalendarEvent',
|
||||||
|
[RightDrawerPages.ViewRecord]: 'Icon123',
|
||||||
|
};
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
|
|
||||||
|
export const RIGHT_DRAWER_PAGE_TITLES = {
|
||||||
|
[RightDrawerPages.CreateActivity]: 'Create Activity',
|
||||||
|
[RightDrawerPages.EditActivity]: 'Edit Activity',
|
||||||
|
[RightDrawerPages.ViewEmailThread]: 'Email Thread',
|
||||||
|
[RightDrawerPages.ViewCalendarEvent]: 'Calendar Event',
|
||||||
|
[RightDrawerPages.ViewRecord]: 'Record Editor',
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||||
|
|
||||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
||||||
@ -9,6 +10,8 @@ import { RightDrawerPages } from '../types/RightDrawerPages';
|
|||||||
|
|
||||||
export const useRightDrawer = () => {
|
export const useRightDrawer = () => {
|
||||||
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||||
|
const [isRightDrawerExpanded] = useRecoilState(isRightDrawerExpandedState);
|
||||||
|
const [isRightDrawerMinimized] = useRecoilState(isRightDrawerMinimizedState);
|
||||||
|
|
||||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||||
|
|
||||||
@ -18,6 +21,7 @@ export const useRightDrawer = () => {
|
|||||||
set(rightDrawerPageState, rightDrawerPage);
|
set(rightDrawerPageState, rightDrawerPage);
|
||||||
set(isRightDrawerExpandedState, false);
|
set(isRightDrawerExpandedState, false);
|
||||||
set(isRightDrawerOpenState, true);
|
set(isRightDrawerOpenState, true);
|
||||||
|
set(isRightDrawerMinimizedState, false);
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -27,6 +31,47 @@ export const useRightDrawer = () => {
|
|||||||
() => {
|
() => {
|
||||||
set(isRightDrawerExpandedState, false);
|
set(isRightDrawerExpandedState, false);
|
||||||
set(isRightDrawerOpenState, false);
|
set(isRightDrawerOpenState, false);
|
||||||
|
set(isRightDrawerMinimizedState, false);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const minimizeRightDrawer = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
() => {
|
||||||
|
set(isRightDrawerExpandedState, false);
|
||||||
|
set(isRightDrawerOpenState, true);
|
||||||
|
set(isRightDrawerMinimizedState, true);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const maximizeRightDrawer = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
() => {
|
||||||
|
set(isRightDrawerMinimizedState, false);
|
||||||
|
set(isRightDrawerExpandedState, false);
|
||||||
|
set(isRightDrawerOpenState, true);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandRightDrawer = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
() => {
|
||||||
|
set(isRightDrawerExpandedState, true);
|
||||||
|
set(isRightDrawerOpenState, true);
|
||||||
|
set(isRightDrawerMinimizedState, false);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const downsizeRightDrawer = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
() => {
|
||||||
|
set(isRightDrawerExpandedState, false);
|
||||||
|
set(isRightDrawerOpenState, true);
|
||||||
|
set(isRightDrawerMinimizedState, false);
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -50,8 +95,14 @@ export const useRightDrawer = () => {
|
|||||||
return {
|
return {
|
||||||
rightDrawerPage,
|
rightDrawerPage,
|
||||||
isRightDrawerOpen,
|
isRightDrawerOpen,
|
||||||
|
isRightDrawerExpanded,
|
||||||
|
isRightDrawerMinimized,
|
||||||
openRightDrawer,
|
openRightDrawer,
|
||||||
closeRightDrawer,
|
closeRightDrawer,
|
||||||
|
minimizeRightDrawer,
|
||||||
|
maximizeRightDrawer,
|
||||||
|
expandRightDrawer,
|
||||||
|
downsizeRightDrawer,
|
||||||
isSameEventThanRightDrawerClose,
|
isSameEventThanRightDrawerClose,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const isRightDrawerMinimizedState = createState<boolean>({
|
||||||
|
key: 'ui/layout/is-right-drawer-minimized',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
@ -3,4 +3,5 @@ export enum RightDrawerPages {
|
|||||||
EditActivity = 'edit-activity',
|
EditActivity = 'edit-activity',
|
||||||
ViewEmailThread = 'view-email-thread',
|
ViewEmailThread = 'view-email-thread',
|
||||||
ViewCalendarEvent = 'view-calendar-event',
|
ViewCalendarEvent = 'view-calendar-event',
|
||||||
|
ViewRecord = 'view-record',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,22 +4,23 @@ import styled from '@emotion/styled';
|
|||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
|
||||||
const StyledOuterContainer = styled.div`
|
const StyledOuterContainer = styled.div<{ isMobile: boolean }>`
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
border-bottom-left-radius: 8px;
|
border-bottom-left-radius: 8px;
|
||||||
border-right: ${({ theme }) =>
|
border-right: ${({ theme, isMobile }) =>
|
||||||
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
|
isMobile ? 'none' : `1px solid ${theme.border.color.medium}`};
|
||||||
border-top-left-radius: 8px;
|
border-top-left-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: ${({ theme }) => theme.spacing(3)};
|
gap: ${({ theme }) => theme.spacing(3)};
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
width: 'auto';
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledInnerContainer = styled.div`
|
const StyledInnerContainer = styled.div<{ isMobile: boolean }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: ${() => (useIsMobile() ? `100%` : '348px')};
|
width: ${({ isMobile }) => (isMobile ? `100%` : '348px')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIntermediateContainer = styled.div`
|
const StyledIntermediateContainer = styled.div`
|
||||||
@ -29,24 +30,30 @@ const StyledIntermediateContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type ShowPageLeftContainerProps = {
|
export type ShowPageLeftContainerProps = {
|
||||||
|
forceMobile: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShowPageLeftContainer = ({
|
export const ShowPageLeftContainer = ({
|
||||||
|
forceMobile = false,
|
||||||
children,
|
children,
|
||||||
}: ShowPageLeftContainerProps) => {
|
}: ShowPageLeftContainerProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile() || forceMobile;
|
||||||
return isMobile ? (
|
return (
|
||||||
<StyledOuterContainer>
|
<StyledOuterContainer isMobile={isMobile}>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
{isMobile ? (
|
||||||
</StyledOuterContainer>
|
<StyledInnerContainer isMobile={isMobile}>
|
||||||
) : (
|
{children}
|
||||||
<StyledOuterContainer>
|
</StyledInnerContainer>
|
||||||
<ScrollWrapper>
|
) : (
|
||||||
<StyledIntermediateContainer>
|
<ScrollWrapper>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
<StyledIntermediateContainer>
|
||||||
</StyledIntermediateContainer>
|
<StyledInnerContainer isMobile={isMobile}>
|
||||||
</ScrollWrapper>
|
{children}
|
||||||
|
</StyledInnerContainer>
|
||||||
|
</StyledIntermediateContainer>
|
||||||
|
</ScrollWrapper>
|
||||||
|
)}
|
||||||
</StyledOuterContainer>
|
</StyledOuterContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,12 +24,12 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
|||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
|
||||||
const StyledShowPageRightContainer = styled.div`
|
const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
overflow: ${() => (useIsMobile() ? 'none' : 'hidden')};
|
overflow: ${(isMobile) => (isMobile ? 'none' : 'hidden')};
|
||||||
width: calc(100% + 4px);
|
width: calc(100% + 4px);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -53,6 +53,8 @@ type ShowPageRightContainerProps = {
|
|||||||
tasks?: boolean;
|
tasks?: boolean;
|
||||||
notes?: boolean;
|
notes?: boolean;
|
||||||
emails?: boolean;
|
emails?: boolean;
|
||||||
|
summary?: JSX.Element;
|
||||||
|
isRightDrawer?: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,8 +65,12 @@ export const ShowPageRightContainer = ({
|
|||||||
notes,
|
notes,
|
||||||
emails,
|
emails,
|
||||||
loading,
|
loading,
|
||||||
|
summary,
|
||||||
|
isRightDrawer = false,
|
||||||
}: ShowPageRightContainerProps) => {
|
}: ShowPageRightContainerProps) => {
|
||||||
const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
|
const { activeTabIdState } = useTabList(
|
||||||
|
TAB_LIST_COMPONENT_ID + isRightDrawer,
|
||||||
|
);
|
||||||
const activeTabId = useRecoilValue(activeTabIdState);
|
const activeTabId = useRecoilValue(activeTabIdState);
|
||||||
|
|
||||||
const shouldDisplayCalendarTab =
|
const shouldDisplayCalendarTab =
|
||||||
@ -80,12 +86,20 @@ export const ShowPageRightContainer = ({
|
|||||||
CoreObjectNameSingular.Company) ||
|
CoreObjectNameSingular.Company) ||
|
||||||
targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Person;
|
targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Person;
|
||||||
|
|
||||||
|
const isMobile = useIsMobile() || isRightDrawer;
|
||||||
|
|
||||||
const TASK_TABS = [
|
const TASK_TABS = [
|
||||||
|
{
|
||||||
|
id: 'summary',
|
||||||
|
title: 'Summary',
|
||||||
|
Icon: IconCheckbox,
|
||||||
|
hide: !isMobile,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'timeline',
|
id: 'timeline',
|
||||||
title: 'Timeline',
|
title: 'Timeline',
|
||||||
Icon: IconTimelineEvent,
|
Icon: IconTimelineEvent,
|
||||||
hide: !timeline,
|
hide: !timeline || isRightDrawer,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'tasks',
|
id: 'tasks',
|
||||||
@ -127,14 +141,15 @@ export const ShowPageRightContainer = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledShowPageRightContainer>
|
<StyledShowPageRightContainer isMobile={isMobile}>
|
||||||
<StyledTabListContainer>
|
<StyledTabListContainer>
|
||||||
<TabList
|
<TabList
|
||||||
loading={loading}
|
loading={loading}
|
||||||
tabListId={TAB_LIST_COMPONENT_ID}
|
tabListId={TAB_LIST_COMPONENT_ID + isRightDrawer}
|
||||||
tabs={TASK_TABS}
|
tabs={TASK_TABS}
|
||||||
/>
|
/>
|
||||||
</StyledTabListContainer>
|
</StyledTabListContainer>
|
||||||
|
{activeTabId === 'summary' && summary}
|
||||||
{activeTabId === 'timeline' && (
|
{activeTabId === 'timeline' && (
|
||||||
<>
|
<>
|
||||||
<TimelineQueryEffect targetableObject={targetableObject} />
|
<TimelineQueryEffect targetableObject={targetableObject} />
|
||||||
@ -157,6 +172,7 @@ export const ShowPageRightContainer = ({
|
|||||||
{activeTabId === 'logs' && (
|
{activeTabId === 'logs' && (
|
||||||
<TimelineActivities targetableObject={targetableObject} />
|
<TimelineActivities targetableObject={targetableObject} />
|
||||||
)}
|
)}
|
||||||
|
{}
|
||||||
</StyledShowPageRightContainer>
|
</StyledShowPageRightContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const StyledContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const TabList = ({ tabs, tabListId, loading }: TabListProps) => {
|
export const TabList = ({ tabs, tabListId, loading }: TabListProps) => {
|
||||||
const initialActiveTabId = tabs[0].id;
|
const initialActiveTabId = tabs.find((tab) => !tab.hide)?.id || '';
|
||||||
|
|
||||||
const { activeTabIdState, setActiveTabId } = useTabList(tabListId);
|
const { activeTabIdState, setActiveTabId } = useTabList(tabListId);
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,6 @@ export const RecordIndexPage = () => {
|
|||||||
|
|
||||||
const handleAddButtonClick = async () => {
|
const handleAddButtonClick = async () => {
|
||||||
setPendingRecordId(v4());
|
setPendingRecordId(v4());
|
||||||
|
|
||||||
setSelectedTableCellEditMode(-1, 0);
|
setSelectedTableCellEditMode(-1, 0);
|
||||||
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
|
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
import { useIcons } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|
||||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
|
||||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||||
import { findOneRecordForShowPageOperationSignatureFactory } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
|
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||||
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
|
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
|
||||||
@ -19,93 +11,29 @@ import { PageHeader } from '@/ui/layout/page/PageHeader';
|
|||||||
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
|
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
|
||||||
import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton';
|
import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton';
|
||||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
|
||||||
|
|
||||||
export const RecordShowPage = () => {
|
export const RecordShowPage = () => {
|
||||||
const { objectNameSingular, objectRecordId } = useParams<{
|
const parameters = useParams<{
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
objectRecordId: string;
|
objectRecordId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
if (!objectNameSingular) {
|
const {
|
||||||
throw new Error(`Object name is not defined`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!objectRecordId) {
|
|
||||||
throw new Error(`Record id is not defined`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
|
||||||
|
|
||||||
const { labelIdentifierFieldMetadataItem } =
|
|
||||||
useLabelIdentifierFieldMetadataItem({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
|
||||||
|
|
||||||
const setEntityFields = useSetRecoilState(
|
|
||||||
recordStoreFamilyState(objectRecordId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
|
||||||
|
|
||||||
const headerIcon = getIcon(objectMetadataItem?.icon);
|
|
||||||
|
|
||||||
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
|
|
||||||
findOneRecordForShowPageOperationSignatureFactory({ objectMetadataItem });
|
|
||||||
|
|
||||||
const { record, loading } = useFindOneRecord({
|
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
objectNameSingular,
|
headerIcon,
|
||||||
recordGqlFields: FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE.fields,
|
loading,
|
||||||
});
|
pageTitle,
|
||||||
|
pageName,
|
||||||
useEffect(() => {
|
isFavorite,
|
||||||
if (!record) {
|
handleFavoriteButtonClick,
|
||||||
return;
|
record,
|
||||||
}
|
objectMetadataItem,
|
||||||
|
} = useRecordShowPage(
|
||||||
setEntityFields(record);
|
parameters.objectNameSingular ?? '',
|
||||||
}, [record, setEntityFields]);
|
parameters.objectRecordId ?? '',
|
||||||
|
|
||||||
const correspondingFavorite = favorites.find(
|
|
||||||
(favorite) => favorite.recordId === objectRecordId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFavorite = isDefined(correspondingFavorite);
|
|
||||||
|
|
||||||
const handleFavoriteButtonClick = async () => {
|
|
||||||
if (!objectNameSingular || !record) return;
|
|
||||||
|
|
||||||
if (isFavorite && isDefined(record)) {
|
|
||||||
deleteFavorite(correspondingFavorite.id);
|
|
||||||
} else {
|
|
||||||
createFavorite(record, objectNameSingular);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const labelIdentifierFieldValue =
|
|
||||||
record?.[labelIdentifierFieldMetadataItem?.name ?? ''];
|
|
||||||
|
|
||||||
const pageName =
|
|
||||||
labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FullName
|
|
||||||
? [
|
|
||||||
labelIdentifierFieldValue?.firstName,
|
|
||||||
labelIdentifierFieldValue?.lastName,
|
|
||||||
].join(' ')
|
|
||||||
: isDefined(labelIdentifierFieldValue)
|
|
||||||
? `${labelIdentifierFieldValue}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const pageTitle = pageName.trim()
|
|
||||||
? `${pageName} - ${capitalize(objectNameSingular)}`
|
|
||||||
: capitalize(objectNameSingular);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordFieldValueSelectorContextProvider>
|
<RecordFieldValueSelectorContextProvider>
|
||||||
<RecordValueSetterEffect recordId={objectRecordId} />
|
<RecordValueSetterEffect recordId={objectRecordId} />
|
||||||
|
|||||||
Reference in New Issue
Block a user