Add optimistic rendering for tasks (#1140)
* Add optimistic rendering for tasks * Refetch activities for lists updates
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
@ -11,6 +13,8 @@ import {
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
|
||||
|
||||
export type OwnProps = {
|
||||
activity: Pick<Activity, 'id'> & {
|
||||
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
|
||||
@ -41,6 +45,8 @@ export function ActivityAssigneePicker({
|
||||
entityType: Entity.User,
|
||||
id: user.id,
|
||||
name: user.displayName,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
avatarType: 'rounded',
|
||||
avatarUrl: user.avatarUrl ?? '',
|
||||
}),
|
||||
@ -48,17 +54,34 @@ export function ActivityAssigneePicker({
|
||||
searchOnFields: ['firstName', 'lastName'],
|
||||
});
|
||||
|
||||
async function handleEntitySelected(
|
||||
const client = useApolloClient();
|
||||
const cachedActivity = client.readFragment({
|
||||
id: `Activity:${activity.id}`,
|
||||
fragment: ACTIVITY_UPDATE_FRAGMENT,
|
||||
});
|
||||
|
||||
function handleEntitySelected(
|
||||
selectedUser: UserForSelect | null | undefined,
|
||||
) {
|
||||
if (selectedUser) {
|
||||
await updateActivity({
|
||||
updateActivity({
|
||||
variables: {
|
||||
where: { id: activity.id },
|
||||
data: {
|
||||
assignee: { connect: { id: selectedUser.id } },
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
...cachedActivity,
|
||||
assignee: {
|
||||
__typename: 'User',
|
||||
...selectedUser,
|
||||
displayName: selectedUser.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useBlockNote } from '@blocknote/react';
|
||||
import styled from '@emotion/styled';
|
||||
@ -8,7 +8,7 @@ import debounce from 'lodash.debounce';
|
||||
import { BlockEditor } from '@/ui/editor/components/BlockEditor';
|
||||
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '../queries/select';
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
|
||||
|
||||
const BlockNoteStyledContainer = styled.div`
|
||||
width: 100%;
|
||||
@ -22,6 +22,12 @@ type OwnProps = {
|
||||
export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const client = useApolloClient();
|
||||
const cachedActivity = client.readFragment({
|
||||
id: `Activity:${activity.id}`,
|
||||
fragment: ACTIVITY_UPDATE_FRAGMENT,
|
||||
});
|
||||
|
||||
const [body, setBody] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -42,12 +48,18 @@ export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
|
||||
body: activityBody,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
...cachedActivity,
|
||||
body: activityBody,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return debounce(onInternalChange, 200);
|
||||
}, [activity, updateActivityMutation, setBody]);
|
||||
}, [updateActivityMutation, activity.id, cachedActivity]);
|
||||
|
||||
const editor: BlockNoteEditor | null = useBlockNote({
|
||||
initialContent: activity.body ? JSON.parse(activity.body) : undefined,
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
||||
import { ActivityComments } from '@/activities/components/ActivityComments';
|
||||
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
||||
import {
|
||||
GET_ACTIVITIES,
|
||||
GET_ACTIVITIES_BY_TARGETS,
|
||||
} from '@/activities/queries';
|
||||
import { GET_ACTIVITIES } from '@/activities/queries';
|
||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
|
||||
import { IconCalendar } from '@/ui/icon/index';
|
||||
@ -24,6 +22,7 @@ import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { ActivityAssigneeEditableField } from '../editable-fields/components/ActivityAssigneeEditableField';
|
||||
import { ActivityRelationEditableField } from '../editable-fields/components/ActivityRelationEditableField';
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
import { ActivityTitle } from './ActivityTitle';
|
||||
@ -96,6 +95,12 @@ export function ActivityEditor({
|
||||
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const client = useApolloClient();
|
||||
const cachedActivity = client.readFragment({
|
||||
id: `Activity:${activity.id}`,
|
||||
fragment: ACTIVITY_UPDATE_FRAGMENT,
|
||||
});
|
||||
|
||||
const updateTitle = useCallback(
|
||||
(newTitle: string) => {
|
||||
updateActivityMutation({
|
||||
@ -107,10 +112,17 @@ export function ActivityEditor({
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
__typename: 'Activity',
|
||||
...cachedActivity,
|
||||
title: newTitle,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
[activity, updateActivityMutation],
|
||||
[activity.id, cachedActivity, updateActivityMutation],
|
||||
);
|
||||
|
||||
const handleActivityCompletionChange = useCallback(
|
||||
@ -124,11 +136,19 @@ export function ActivityEditor({
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
__typename: 'Activity',
|
||||
...cachedActivity,
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''],
|
||||
});
|
||||
setCompletedAt(value ? new Date().toISOString() : null);
|
||||
},
|
||||
[activity, updateActivityMutation],
|
||||
[activity.id, cachedActivity, updateActivityMutation],
|
||||
);
|
||||
|
||||
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||
|
||||
@ -1,32 +1,43 @@
|
||||
import { useCallback } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import {
|
||||
GET_ACTIVITIES,
|
||||
GET_ACTIVITIES_BY_TARGETS,
|
||||
} from '@/activities/queries';
|
||||
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../queries/update';
|
||||
|
||||
type Task = Pick<Activity, 'id'>;
|
||||
|
||||
export function useCompleteTask(task: Task) {
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const client = useApolloClient();
|
||||
const cachedTask = client.readFragment({
|
||||
id: `Activity:${task.id}`,
|
||||
fragment: ACTIVITY_UPDATE_FRAGMENT,
|
||||
});
|
||||
|
||||
console.log('cachedTask', cachedTask);
|
||||
|
||||
const completeTask = useCallback(
|
||||
(value: boolean) => {
|
||||
const completedAt = value ? new Date().toISOString() : null;
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
where: { id: task.id },
|
||||
data: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
completedAt,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
...cachedTask,
|
||||
completedAt,
|
||||
},
|
||||
},
|
||||
refetchQueries: [
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
getOperationName(GET_ACTIVITIES) ?? '',
|
||||
],
|
||||
});
|
||||
},
|
||||
[task, updateActivityMutation],
|
||||
[cachedTask, task.id, updateActivityMutation],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -60,24 +60,30 @@ export const DELETE_ACTIVITY = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ACTIVITY_UPDATE_FRAGMENT = gql`
|
||||
fragment ActivityUpdateParts on Activity {
|
||||
id
|
||||
body
|
||||
title
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_ACTIVITY = gql`
|
||||
mutation UpdateActivity(
|
||||
$where: ActivityWhereUniqueInput!
|
||||
$data: ActivityUpdateInput!
|
||||
) {
|
||||
updateOneActivity(where: $where, data: $data) {
|
||||
id
|
||||
body
|
||||
title
|
||||
type
|
||||
completedAt
|
||||
dueAt
|
||||
assignee {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
}
|
||||
...ActivityUpdateParts
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user