feat: rename comment thread into activity (#939)
* feat: rename commentThread into activity server * feat: rename commentThread into activity front * feat: migration only create tables feat: migration only create tables * Update activities * fix: rebase partial fix * fix: all rebase problems and drop activity target alter * fix: lint * Update migration * Update migration * Fix conflicts * Fix conflicts --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -6,24 +6,21 @@ import styled from '@emotion/styled';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { BlockEditor } from '@/ui/editor/components/BlockEditor';
|
||||
import {
|
||||
CommentThread,
|
||||
useUpdateCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '../queries/select';
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '../queries/select';
|
||||
|
||||
const BlockNoteStyledContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<CommentThread, 'id' | 'body'>;
|
||||
onChange?: (commentThreadBody: string) => void;
|
||||
activity: Pick<Activity, 'id' | 'body'>;
|
||||
onChange?: (activityBody: string) => void;
|
||||
};
|
||||
|
||||
export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) {
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const [body, setBody] = useState<string | null>(null);
|
||||
|
||||
@ -34,26 +31,22 @@ export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) {
|
||||
}, [body, onChange]);
|
||||
|
||||
const debounceOnChange = useMemo(() => {
|
||||
function onInternalChange(commentThreadBody: string) {
|
||||
setBody(commentThreadBody);
|
||||
updateCommentThreadMutation({
|
||||
function onInternalChange(activityBody: string) {
|
||||
setBody(activityBody);
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: commentThread.id,
|
||||
body: commentThreadBody,
|
||||
id: activity.id,
|
||||
body: activityBody,
|
||||
},
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
],
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
return debounce(onInternalChange, 200);
|
||||
}, [commentThread, updateCommentThreadMutation, setBody]);
|
||||
}, [activity, updateActivityMutation, setBody]);
|
||||
|
||||
const editor: BlockNoteEditor | null = useBlockNote({
|
||||
initialContent: commentThread.body
|
||||
? JSON.parse(commentThread.body)
|
||||
: undefined,
|
||||
initialContent: activity.body ? JSON.parse(activity.body) : undefined,
|
||||
editorDOMAttributes: { class: 'editor' },
|
||||
onEditorContentChange: (editor) => {
|
||||
debounceOnChange(JSON.stringify(editor.topLevelBlocks) ?? '');
|
||||
@ -6,15 +6,15 @@ import { v4 } from 'uuid';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { AutosizeTextInput } from '@/ui/input/components/AutosizeTextInput';
|
||||
import { CommentThread, useCreateCommentMutation } from '~/generated/graphql';
|
||||
import { Activity, useCreateCommentMutation } from '~/generated/graphql';
|
||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||
|
||||
import { Comment } from '../comment/Comment';
|
||||
import { GET_COMMENT_THREAD } from '../queries';
|
||||
import { GET_ACTIVITY } from '../queries';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<CommentThread, 'id'> & {
|
||||
activity: Pick<Activity, 'id'> & {
|
||||
comments: Array<CommentForDrawer>;
|
||||
};
|
||||
};
|
||||
@ -52,7 +52,7 @@ const StyledThreadCommentTitle = styled.div`
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
export function CommentThreadComments({ commentThread }: OwnProps) {
|
||||
export function ActivityComments({ activity }: OwnProps) {
|
||||
const [createCommentMutation] = useCreateCommentMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
@ -69,21 +69,21 @@ export function CommentThreadComments({ commentThread }: OwnProps) {
|
||||
variables: {
|
||||
commentId: v4(),
|
||||
authorId: currentUser?.id ?? '',
|
||||
commentThreadId: commentThread?.id ?? '',
|
||||
activityId: activity?.id ?? '',
|
||||
commentText: commentText,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_COMMENT_THREAD) ?? ''],
|
||||
refetchQueries: [getOperationName(GET_ACTIVITY) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{commentThread?.comments.length > 0 && (
|
||||
{activity?.comments.length > 0 && (
|
||||
<>
|
||||
<StyledThreadItemListContainer>
|
||||
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
|
||||
{commentThread?.comments?.map((comment) => (
|
||||
{activity?.comments?.map((comment) => (
|
||||
<Comment key={comment.id} comment={comment} />
|
||||
))}
|
||||
</StyledThreadItemListContainer>
|
||||
@ -4,17 +4,17 @@ import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
|
||||
|
||||
type CommentThreadCreateButtonProps = {
|
||||
type ActivityCreateButtonProps = {
|
||||
onNoteClick?: () => void;
|
||||
onTaskClick?: () => void;
|
||||
onActivityClick?: () => void;
|
||||
};
|
||||
|
||||
export function CommentThreadCreateButton({
|
||||
export function ActivityCreateButton({
|
||||
onNoteClick,
|
||||
onTaskClick,
|
||||
onActivityClick,
|
||||
}: CommentThreadCreateButtonProps) {
|
||||
}: ActivityCreateButtonProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonGroup variant={ButtonVariant.Secondary}>
|
||||
@ -2,26 +2,26 @@ import React, { useCallback, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CommentThreadBodyEditor } from '@/activities/components/CommentThreadBodyEditor';
|
||||
import { CommentThreadComments } from '@/activities/components/CommentThreadComments';
|
||||
import { CommentThreadRelationPicker } from '@/activities/components/CommentThreadRelationPicker';
|
||||
import { CommentThreadTypeDropdown } from '@/activities/components/CommentThreadTypeDropdown';
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
||||
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
||||
import { ActivityComments } from '@/activities/components/ActivityComments';
|
||||
import { ActivityRelationPicker } from '@/activities/components/ActivityRelationPicker';
|
||||
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconArrowUpRight } from '@/ui/icon/index';
|
||||
import {
|
||||
CommentThread,
|
||||
CommentThreadTarget,
|
||||
useUpdateCommentThreadMutation,
|
||||
Activity,
|
||||
ActivityTarget,
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { CommentThreadActionBar } from '../right-drawer/components/CommentThreadActionBar';
|
||||
import { ActivityActionBar } from '../right-drawer/components/ActivityActionBar';
|
||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||
|
||||
import { CommentThreadTitle } from './CommentThreadTitle';
|
||||
import { ActivityTitle } from './ActivityTitle';
|
||||
|
||||
import '@blocknote/core/style.css';
|
||||
|
||||
@ -65,64 +65,57 @@ const StyledTopActionsContainer = styled.div`
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<
|
||||
CommentThread,
|
||||
'id' | 'title' | 'body' | 'type' | 'completedAt'
|
||||
> & {
|
||||
activity: Pick<Activity, 'id' | 'title' | 'body' | 'type' | 'completedAt'> & {
|
||||
comments?: Array<CommentForDrawer> | null;
|
||||
} & {
|
||||
commentThreadTargets?: Array<
|
||||
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
activityTargets?: Array<
|
||||
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
> | null;
|
||||
};
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
};
|
||||
|
||||
export function CommentThreadEditor({
|
||||
commentThread,
|
||||
export function ActivityEditor({
|
||||
activity,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
||||
}: OwnProps) {
|
||||
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [title, setTitle] = useState<string | null>(commentThread.title ?? '');
|
||||
const [title, setTitle] = useState<string | null>(activity.title ?? '');
|
||||
const [completedAt, setCompletedAt] = useState<string | null>(
|
||||
commentThread.completedAt ?? '',
|
||||
activity.completedAt ?? '',
|
||||
);
|
||||
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const updateTitle = useCallback(
|
||||
(newTitle: string) => {
|
||||
updateCommentThreadMutation({
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: commentThread.id,
|
||||
id: activity.id,
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
],
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
},
|
||||
[commentThread, updateCommentThreadMutation],
|
||||
[activity, updateActivityMutation],
|
||||
);
|
||||
|
||||
const handleActivityCompletionChange = useCallback(
|
||||
(value: boolean) => {
|
||||
updateCommentThreadMutation({
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: commentThread.id,
|
||||
id: activity.id,
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
],
|
||||
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||
});
|
||||
setCompletedAt(value ? new Date().toISOString() : null);
|
||||
},
|
||||
[commentThread, updateCommentThreadMutation],
|
||||
[activity, updateActivityMutation],
|
||||
);
|
||||
|
||||
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||
@ -135,7 +128,7 @@ export function CommentThreadEditor({
|
||||
}
|
||||
}
|
||||
|
||||
if (!commentThread) {
|
||||
if (!activity) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -144,13 +137,13 @@ export function CommentThreadEditor({
|
||||
<StyledUpperPartContainer>
|
||||
<StyledTopContainer>
|
||||
<StyledTopActionsContainer>
|
||||
<CommentThreadTypeDropdown commentThread={commentThread} />
|
||||
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
||||
<ActivityTypeDropdown activity={activity} />
|
||||
<ActivityActionBar activityId={activity?.id ?? ''} />
|
||||
</StyledTopActionsContainer>
|
||||
<CommentThreadTitle
|
||||
<ActivityTitle
|
||||
title={title ?? ''}
|
||||
completed={!!completedAt}
|
||||
type={commentThread.type}
|
||||
type={activity.type}
|
||||
onTitleChange={(newTitle) => {
|
||||
setTitle(newTitle);
|
||||
setHasUserManuallySetTitle(true);
|
||||
@ -162,11 +155,10 @@ export function CommentThreadEditor({
|
||||
<PropertyBoxItem
|
||||
icon={<IconArrowUpRight />}
|
||||
value={
|
||||
<CommentThreadRelationPicker
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
commentThreadTargets:
|
||||
commentThread.commentThreadTargets ?? [],
|
||||
<ActivityRelationPicker
|
||||
activity={{
|
||||
id: activity.id,
|
||||
activityTargets: activity.activityTargets ?? [],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
@ -174,16 +166,16 @@ export function CommentThreadEditor({
|
||||
/>
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<CommentThreadBodyEditor
|
||||
commentThread={commentThread}
|
||||
<ActivityBodyEditor
|
||||
activity={activity}
|
||||
onChange={updateTitleFromBody}
|
||||
/>
|
||||
</StyledUpperPartContainer>
|
||||
{showComment && (
|
||||
<CommentThreadComments
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
comments: commentThread.comments ?? [],
|
||||
<ActivityComments
|
||||
activity={{
|
||||
id: activity.id,
|
||||
comments: activity.comments ?? [],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -18,19 +18,15 @@ import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
import { MultipleEntitySelect } from '@/ui/relation-picker/components/MultipleEntitySelect';
|
||||
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import {
|
||||
CommentableType,
|
||||
CommentThread,
|
||||
CommentThreadTarget,
|
||||
} from '~/generated/graphql';
|
||||
import { Activity, ActivityTarget, CommentableType } from '~/generated/graphql';
|
||||
|
||||
import { useHandleCheckableCommentThreadTargetChange } from '../hooks/useHandleCheckableCommentThreadTargetChange';
|
||||
import { useHandleCheckableActivityTargetChange } from '../hooks/useHandleCheckableActivityTargetChange';
|
||||
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../utils/flatMapAndSortEntityForSelectArrayByName';
|
||||
|
||||
type OwnProps = {
|
||||
commentThread?: Pick<CommentThread, 'id'> & {
|
||||
commentThreadTargets: Array<
|
||||
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
activity?: Pick<Activity, 'id'> & {
|
||||
activityTargets: Array<
|
||||
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
>;
|
||||
};
|
||||
};
|
||||
@ -75,7 +71,7 @@ const StyledMenuWrapper = styled.div`
|
||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||
`;
|
||||
|
||||
export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
||||
export function ActivityRelationPicker({ activity }: OwnProps) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const [selectedEntityIds, setSelectedEntityIds] = useState<
|
||||
@ -88,17 +84,18 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
||||
|
||||
const initialPeopleIds = useMemo(
|
||||
() =>
|
||||
commentThread?.commentThreadTargets
|
||||
activity?.activityTargets
|
||||
?.filter((relation) => relation.commentableType === 'Person')
|
||||
.map((relation) => relation.commentableId) ?? [],
|
||||
[commentThread?.commentThreadTargets],
|
||||
[activity?.activityTargets],
|
||||
);
|
||||
|
||||
const initialCompanyIds = useMemo(
|
||||
() =>
|
||||
commentThread?.commentThreadTargets
|
||||
activity?.activityTargets
|
||||
?.filter((relation) => relation.commentableType === 'Company')
|
||||
.map((relation) => relation.commentableId) ?? [],
|
||||
[commentThread?.commentThreadTargets],
|
||||
[activity?.activityTargets],
|
||||
);
|
||||
|
||||
const initialSelectedEntityIds = useMemo(
|
||||
@ -135,8 +132,8 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
||||
companiesForMultiSelect.entitiesToSelect,
|
||||
]);
|
||||
|
||||
const handleCheckItemsChange = useHandleCheckableCommentThreadTargetChange({
|
||||
commentThread,
|
||||
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
|
||||
activity,
|
||||
});
|
||||
|
||||
const exitEditMode = useCallback(() => {
|
||||
@ -51,7 +51,7 @@ type OwnProps = {
|
||||
onCompletionChange: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export function CommentThreadTitle({
|
||||
export function ActivityTitle({
|
||||
title,
|
||||
completed,
|
||||
type,
|
||||
@ -7,17 +7,17 @@ import {
|
||||
ChipVariant,
|
||||
} from '@/ui/chip/components/Chip';
|
||||
import { IconPhone } from '@/ui/icon';
|
||||
import { CommentThread } from '~/generated/graphql';
|
||||
import { Activity } from '~/generated/graphql';
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<CommentThread, 'type'>;
|
||||
activity: Pick<Activity, 'type'>;
|
||||
};
|
||||
|
||||
export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
|
||||
export function ActivityTypeDropdown({ activity }: OwnProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Chip
|
||||
label={commentThread.type}
|
||||
label={activity.type}
|
||||
leftComponent={<IconPhone size={theme.icon.size.md} />}
|
||||
size={ChipSize.Large}
|
||||
accent={ChipAccent.TextSecondary}
|
||||
@ -4,17 +4,17 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
|
||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||
|
||||
import { CommentThreadRelationPicker } from '../CommentThreadRelationPicker';
|
||||
import { ActivityRelationPicker } from '../ActivityRelationPicker';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 400px;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof CommentThreadRelationPicker> = {
|
||||
title: 'Modules/Comments/CommentThreadRelationPicker',
|
||||
component: CommentThreadRelationPicker,
|
||||
const meta: Meta<typeof ActivityRelationPicker> = {
|
||||
title: 'Modules/Comments/ActivityRelationPicker',
|
||||
component: ActivityRelationPicker,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MemoryRouter>
|
||||
@ -25,13 +25,13 @@ const meta: Meta<typeof CommentThreadRelationPicker> = {
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
args: { commentThread: mockedCommentThreads[0] },
|
||||
args: { activity: mockedActivities[0] },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CommentThreadRelationPicker>;
|
||||
type Story = StoryObj<typeof ActivityRelationPicker>;
|
||||
|
||||
export const Default: Story = {};
|
||||
Reference in New Issue
Block a user