Migrate activities (#2545)
* Start * Migrate activities to flexible schema
This commit is contained in:
@@ -1,26 +1,23 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import {
|
||||
Activity,
|
||||
useGetWorkspaceMembersLazyQuery,
|
||||
User,
|
||||
useSearchUserQuery,
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../graphql/fragments/activityUpdateFragment';
|
||||
import { GET_ACTIVITIES } from '../graphql/queries/getActivities';
|
||||
|
||||
export type ActivityAssigneePickerProps = {
|
||||
activity: Pick<Activity, 'id'> & {
|
||||
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
|
||||
accountOwner?: Pick<
|
||||
WorkspaceMember,
|
||||
'id' | 'firstName' | 'lastName'
|
||||
> | null;
|
||||
};
|
||||
onSubmit?: () => void;
|
||||
onCancel?: () => void;
|
||||
@@ -38,7 +35,9 @@ export const ActivityAssigneePicker = ({
|
||||
const [relationPickerSearchFilter] = useRecoilScopedState(
|
||||
relationPickerSearchFilterScopedState,
|
||||
);
|
||||
const [updateActivity] = useUpdateActivityMutation();
|
||||
const { updateOneObject } = useUpdateOneObjectRecord({
|
||||
objectNameSingular: 'ActivityV2',
|
||||
});
|
||||
const [getWorkspaceMember] = useGetWorkspaceMembersLazyQuery();
|
||||
|
||||
const users = useFilteredSearchEntityQuery({
|
||||
@@ -63,12 +62,6 @@ export const ActivityAssigneePicker = ({
|
||||
selectedIds: activity?.accountOwner?.id ? [activity?.accountOwner?.id] : [],
|
||||
});
|
||||
|
||||
const client = useApolloClient();
|
||||
const cachedActivity = client.readFragment({
|
||||
id: `Activity:${activity.id}`,
|
||||
fragment: ACTIVITY_UPDATE_FRAGMENT,
|
||||
});
|
||||
|
||||
const handleEntitySelected = async (
|
||||
selectedUser: UserForSelect | null | undefined,
|
||||
) => {
|
||||
@@ -83,28 +76,14 @@ export const ActivityAssigneePicker = ({
|
||||
})
|
||||
).data?.workspaceMembers?.[0];
|
||||
|
||||
updateActivity({
|
||||
variables: {
|
||||
where: { id: activity.id },
|
||||
data: {
|
||||
assignee: { connect: { id: selectedUser.id } },
|
||||
workspaceMemberAssignee: {
|
||||
connect: { id: workspaceMemberAssignee?.id },
|
||||
},
|
||||
updateOneObject?.({
|
||||
idToUpdate: activity.id,
|
||||
input: {
|
||||
assignee: { connect: { id: selectedUser.id } },
|
||||
workspaceMemberAssignee: {
|
||||
connect: { id: workspaceMemberAssignee?.id },
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
...cachedActivity,
|
||||
assignee: {
|
||||
__typename: 'User',
|
||||
...selectedUser,
|
||||
displayName: selectedUser.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useBlockNote } from '@blocknote/react';
|
||||
import styled from '@emotion/styled';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../graphql/fragments/activityUpdateFragment';
|
||||
|
||||
const StyledBlockNoteStyledContainer = styled.div`
|
||||
width: 100%;
|
||||
@@ -23,15 +21,10 @@ export const ActivityBodyEditor = ({
|
||||
activity,
|
||||
onChange,
|
||||
}: ActivityBodyEditorProps) => {
|
||||
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 { updateOneObject } = useUpdateOneObjectRecord({
|
||||
objectNameSingular: 'ActivityV2',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (body) {
|
||||
@@ -42,27 +35,16 @@ export const ActivityBodyEditor = ({
|
||||
const debounceOnChange = useMemo(() => {
|
||||
const onInternalChange = (activityBody: string) => {
|
||||
setBody(activityBody);
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
body: activityBody,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
...cachedActivity,
|
||||
body: activityBody,
|
||||
},
|
||||
updateOneObject?.({
|
||||
idToUpdate: activity.id,
|
||||
input: {
|
||||
body: activityBody,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return debounce(onInternalChange, 200);
|
||||
}, [updateActivityMutation, activity.id, cachedActivity]);
|
||||
}, [updateOneObject, activity.id]);
|
||||
|
||||
const editor: BlockNoteEditor | null = useBlockNote({
|
||||
initialContent: activity.body ? JSON.parse(activity.body) : undefined,
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { Comment } from '@/activities/comment/Comment';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { Comment as CommentType } from '@/activities/types/Comment';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
|
||||
import {
|
||||
AutosizeTextInput,
|
||||
AutosizeTextInputVariant,
|
||||
} from '@/ui/input/components/AutosizeTextInput';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { Activity, useCreateCommentMutation } from '~/generated/graphql';
|
||||
|
||||
import { Comment } from '../comment/Comment';
|
||||
import { GET_ACTIVITY } from '../graphql/queries/getActivity';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
type ActivityCommentsProps = {
|
||||
activity: Pick<Activity, 'id'> & {
|
||||
comments: Array<CommentForDrawer>;
|
||||
comments: Array<CommentType>;
|
||||
};
|
||||
scrollableContainerRef: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
@@ -63,8 +61,10 @@ export const ActivityComments = ({
|
||||
activity,
|
||||
scrollableContainerRef,
|
||||
}: ActivityCommentsProps) => {
|
||||
const [createCommentMutation] = useCreateCommentMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const { createOneObject } = useCreateOneObjectRecord({
|
||||
objectNamePlural: 'commentsV2',
|
||||
});
|
||||
|
||||
if (!currentUser) {
|
||||
return <></>;
|
||||
@@ -75,21 +75,12 @@ export const ActivityComments = ({
|
||||
return;
|
||||
}
|
||||
|
||||
createCommentMutation({
|
||||
variables: {
|
||||
commentId: v4(),
|
||||
authorId: currentUser?.id ?? '',
|
||||
activityId: activity?.id ?? '',
|
||||
commentText: commentText,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITY) ?? ''],
|
||||
onCompleted: () => {
|
||||
setTimeout(() => {
|
||||
handleFocus();
|
||||
}, 100);
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
createOneObject?.({
|
||||
commentId: v4(),
|
||||
authorId: currentUser?.id ?? '',
|
||||
activityId: activity?.id ?? '',
|
||||
commentText: commentText,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
import React, { useCallback, useRef, 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 } from '@/activities/graphql/queries/getActivities';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { Comment } from '@/activities/types/Comment';
|
||||
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||
import { PropertyBox } from '@/ui/object/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import {
|
||||
Activity,
|
||||
ActivityTarget,
|
||||
ActivityType,
|
||||
User,
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { ActivityAssigneeEditableField } from '../editable-fields/components/ActivityAssigneeEditableField';
|
||||
import { ActivityEditorDateField } from '../editable-fields/components/ActivityEditorDateField';
|
||||
import { ActivityRelationEditableField } from '../editable-fields/components/ActivityRelationEditableField';
|
||||
import { ACTIVITY_UPDATE_FRAGMENT } from '../graphql/fragments/activityUpdateFragment';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
import { ActivityTitle } from './ActivityTitle';
|
||||
|
||||
@@ -65,14 +58,16 @@ type ActivityEditorProps = {
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'type' | 'completedAt' | 'dueAt'
|
||||
> & {
|
||||
comments?: Array<CommentForDrawer> | null;
|
||||
comments?: Array<Comment> | null;
|
||||
} & {
|
||||
assignee?: Pick<
|
||||
User,
|
||||
'id' | 'firstName' | 'lastName' | 'displayName'
|
||||
WorkspaceMember,
|
||||
'id' | 'firstName' | 'lastName' | 'avatarUrl'
|
||||
> | null;
|
||||
} & {
|
||||
activityTargets?: Array<Pick<ActivityTarget, 'id'>> | null;
|
||||
activityTargets?: Array<
|
||||
Pick<ActivityTarget, 'id' | 'companyId' | 'personId'>
|
||||
> | null;
|
||||
};
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
@@ -91,64 +86,32 @@ export const ActivityEditor = ({
|
||||
activity.completedAt ?? '',
|
||||
);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const client = useApolloClient();
|
||||
const cachedActivity = client.readFragment({
|
||||
id: `Activity:${activity.id}`,
|
||||
fragment: ACTIVITY_UPDATE_FRAGMENT,
|
||||
const { updateOneObject } = useUpdateOneObjectRecord({
|
||||
objectNamePlural: 'activitiesV2',
|
||||
});
|
||||
|
||||
const updateTitle = useCallback(
|
||||
(newTitle: string) => {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
updateOneObject?.({
|
||||
idToUpdate: activity.id,
|
||||
input: {
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
__typename: 'Activity',
|
||||
...cachedActivity,
|
||||
title: newTitle,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''],
|
||||
});
|
||||
},
|
||||
[activity.id, cachedActivity, updateActivityMutation],
|
||||
[activity.id, updateOneObject],
|
||||
);
|
||||
|
||||
const handleActivityCompletionChange = useCallback(
|
||||
(value: boolean) => {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
where: {
|
||||
id: activity.id,
|
||||
},
|
||||
data: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
updateOneObject?.({
|
||||
idToUpdate: activity.id,
|
||||
input: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
__typename: 'Activity',
|
||||
...cachedActivity,
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''],
|
||||
});
|
||||
setCompletedAt(value ? new Date().toISOString() : null);
|
||||
},
|
||||
[activity.id, cachedActivity, updateActivityMutation],
|
||||
[activity.id, updateOneObject],
|
||||
);
|
||||
|
||||
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||
@@ -182,7 +145,7 @@ export const ActivityEditor = ({
|
||||
onCompletionChange={handleActivityCompletionChange}
|
||||
/>
|
||||
<PropertyBox>
|
||||
{activity.type === ActivityType.Task && (
|
||||
{activity.type === 'Task' && (
|
||||
<>
|
||||
<RecoilScope>
|
||||
<ActivityEditorDateField activityId={activity.id} />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
import { PersonChip } from '@/people/components/PersonChip';
|
||||
import { ActivityTarget, Company, Person } from '~/generated/graphql';
|
||||
import { Company, Person } from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -16,7 +17,10 @@ export const ActivityTargetChips = ({
|
||||
}: {
|
||||
targets?: Array<
|
||||
Pick<ActivityTarget, 'id'> & {
|
||||
person?: Pick<Person, 'id' | 'displayName' | 'avatarUrl'> | null;
|
||||
person?: Pick<
|
||||
Person,
|
||||
'id' | 'firstName' | 'lastName' | 'avatarUrl'
|
||||
> | null;
|
||||
company?: Pick<Company, 'id' | 'domainName' | 'name'> | null;
|
||||
}
|
||||
> | null;
|
||||
@@ -43,7 +47,7 @@ export const ActivityTargetChips = ({
|
||||
<PersonChip
|
||||
key={person.id}
|
||||
id={person.id}
|
||||
name={person.displayName}
|
||||
name={person.firstName + ' ' + person.lastName}
|
||||
pictureUrl={person.avatarUrl ?? undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxShape,
|
||||
CheckboxSize,
|
||||
} from '@/ui/input/components/Checkbox';
|
||||
import { ActivityType } from '~/generated/graphql';
|
||||
|
||||
const StyledEditableTitleInput = styled.input<{
|
||||
completed: boolean;
|
||||
@@ -60,7 +60,7 @@ export const ActivityTitle = ({
|
||||
onCompletionChange,
|
||||
}: ActivityTitleProps) => (
|
||||
<StyledContainer>
|
||||
{type === ActivityType.Task && (
|
||||
{type === 'Task' && (
|
||||
<StyledCheckboxContainer onClick={() => onCompletionChange(!completed)}>
|
||||
<Checkbox
|
||||
size={CheckboxSize.Large}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import {
|
||||
Chip,
|
||||
ChipAccent,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
ChipVariant,
|
||||
} from '@/ui/display/chip/components/Chip';
|
||||
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
||||
import { Activity, ActivityType } from '~/generated/graphql';
|
||||
|
||||
type ActivityTypeDropdownProps = {
|
||||
activity: Pick<Activity, 'type'>;
|
||||
@@ -21,7 +21,7 @@ export const ActivityTypeDropdown = ({
|
||||
<Chip
|
||||
label={activity.type}
|
||||
leftComponent={
|
||||
activity.type === ActivityType.Note ? (
|
||||
activity.type === 'Note' ? (
|
||||
<IconNotes size={theme.icon.size.md} />
|
||||
) : (
|
||||
<IconCheckbox size={theme.icon.size.md} />
|
||||
|
||||
Reference in New Issue
Block a user