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