Feat/activity optimistic activities (#4009)

* Fix naming

* Fixed cache.evict bug for relation target deletion

* Fixed cascade delete activity targets

* Working version

* Fix

* fix

* WIP

* Fixed optimistic effect target inline cell

* Removed openCreateActivityDrawer v1

* Ok for timeline

* Removed console.log

* Fix update record optimistic effect

* Refactored activity queries into useActivities for everything

* Fixed bugs

* Cleaned

* Fix lint

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-02-20 14:20:45 +01:00
committed by GitHub
parent 6fb0099eb3
commit 36a6558289
68 changed files with 1435 additions and 630 deletions

View File

@ -1,17 +1,22 @@
import { useApolloClient } from '@apollo/client';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { isNonEmptyArray } from '@sniptt/guards';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
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 { useRemoveFromTimelineActivitiesQueries } from '@/activities/timeline/hooks/useRemoveFromTimelineActivitiesQueries';
import { timelineTargetableObjectState } from '@/activities/timeline/states/timelineTargetableObjectState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
import { IconPlus, IconTrash } from '@/ui/display/icon';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
@ -24,6 +29,8 @@ const StyledButtonContainer = styled.div`
export const ActivityActionBar = () => {
const viewableActivityId = useRecoilValue(viewableActivityIdState);
const activityInDrawer = useRecoilValue(activityInDrawerState);
const activityTargetableEntityArray = useRecoilValue(
activityTargetableEntityArrayState,
);
@ -33,27 +40,52 @@ export const ActivityActionBar = () => {
refetchFindManyQuery: true,
});
const { deleteManyRecords: deleteManyActivityTargets } = useDeleteManyRecords(
{
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
refetchFindManyQuery: true,
},
);
const [temporaryActivityForEditor, setTemporaryActivityForEditor] =
useRecoilState(temporaryActivityForEditorState);
const { deleteActivityFromCache } = useDeleteActivityFromCache();
const [isCreatingActivity] = useRecoilState(isCreatingActivityState);
const apolloClient = useApolloClient();
const [isActivityInCreateMode] = useRecoilState(isActivityInCreateModeState);
const [isUpsertingActivityInDB] = useRecoilState(
isUpsertingActivityInDBState,
);
const timelineTargetableObject = useRecoilValue(
timelineTargetableObjectState,
);
const openCreateActivity = useOpenCreateActivityDrawer();
const { removeFromTimelineActivitiesQueries } =
useRemoveFromTimelineActivitiesQueries();
const deleteActivity = () => {
if (viewableActivityId) {
if (isCreatingActivity && isDefined(temporaryActivityForEditor)) {
if (isActivityInCreateMode && isDefined(temporaryActivityForEditor)) {
deleteActivityFromCache(temporaryActivityForEditor);
setTemporaryActivityForEditor(null);
} else {
deleteOneActivity?.(viewableActivityId);
// TODO: find a better way to do this with custom optimistic rendering for activities
apolloClient.refetchQueries({
include: ['FindManyActivities'],
});
if (activityInDrawer) {
const activityTargetIdsToDelete =
activityInDrawer?.activityTargets.map(mapToRecordId) ?? [];
if (isDefined(timelineTargetableObject)) {
removeFromTimelineActivitiesQueries({
activityTargetsToRemove: activityInDrawer?.activityTargets ?? [],
activityIdToRemove: viewableActivityId,
});
}
if (isNonEmptyArray(activityTargetIdsToDelete)) {
deleteManyActivityTargets(activityTargetIdsToDelete);
}
deleteOneActivity?.(viewableActivityId);
}
}
}
@ -66,17 +98,19 @@ export const ActivityActionBar = () => {
const addActivity = () => {
setIsRightDrawerOpen(false);
if (record) {
if (record && timelineTargetableObject) {
openCreateActivity({
type: record.type,
assigneeId: isNonEmptyString(record.assigneeId)
? record.assigneeId
: undefined,
customAssignee: record.assignee,
targetableObjects: activityTargetableEntityArray,
});
}
};
const actionsAreDisabled = isUpsertingActivityInDB;
const isCreateActionDisabled = isActivityInCreateMode;
return (
<StyledButtonContainer>
<IconButton
@ -84,12 +118,14 @@ export const ActivityActionBar = () => {
onClick={addActivity}
size="medium"
variant="secondary"
disabled={actionsAreDisabled || isCreateActionDisabled}
/>
<IconButton
Icon={IconTrash}
onClick={deleteActivity}
size="medium"
variant="secondary"
disabled={actionsAreDisabled}
/>
</StyledButtonContainer>
);

View File

@ -24,11 +24,11 @@ export const RightDrawerActivity = ({
showComment = true,
fillTitleFromBody = false,
}: RightDrawerActivityProps) => {
const { activity } = useActivityById({
const { activity, loading } = useActivityById({
activityId,
});
if (!activity) {
if (!activity || loading) {
return <></>;
}