Right drawer to edit records (#5551)

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

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

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

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

View File

@ -1,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}

View File

@ -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>
);
};

View File

@ -1,27 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { RightDrawerActivityTopBar } from '../RightDrawerActivityTopBar';
const meta: Meta<typeof RightDrawerActivityTopBar> = {
title: 'Modules/Activities/RightDrawer/RightDrawerActivityTopBar',
component: RightDrawerActivityTopBar,
decorators: [
(Story) => (
<div style={{ width: '500px' }}>
<Story />
</div>
),
ComponentWithRouterDecorator,
],
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof RightDrawerActivityTopBar>;
export const Default: Story = {};

View File

@ -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}
/>

View File

@ -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} />
)}
</>
);