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