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:
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { CommentThreadActionBar } from '../../right-drawer/components/CommentThreadActionBar';
|
||||
import { ActivityActionBar } from '../../right-drawer/components/ActivityActionBar';
|
||||
import { Comment } from '../Comment';
|
||||
|
||||
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
||||
@ -15,7 +15,7 @@ const meta: Meta<typeof Comment> = {
|
||||
actionBar: {
|
||||
type: 'boolean',
|
||||
mapping: {
|
||||
true: <CommentThreadActionBar commentThreadId="test-id" />,
|
||||
true: <ActivityActionBar activityId="test-id" />,
|
||||
false: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar';
|
||||
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { avatarUrl } from '~/testing/mock-data/users';
|
||||
|
||||
@ -17,7 +17,7 @@ const meta: Meta<typeof CommentHeader> = {
|
||||
actionBar: {
|
||||
type: 'boolean',
|
||||
mapping: {
|
||||
true: <CommentThreadActionBar commentThreadId="test-id" />,
|
||||
true: <ActivityActionBar activityId="test-id" />,
|
||||
false: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
@ -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 = {};
|
||||
@ -0,0 +1,84 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { GET_COMPANIES } from '@/companies/queries';
|
||||
import { GET_PEOPLE } from '@/people/queries';
|
||||
import {
|
||||
Activity,
|
||||
ActivityTarget,
|
||||
useAddActivityTargetsOnActivityMutation,
|
||||
useRemoveActivityTargetsOnActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '../queries';
|
||||
import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect';
|
||||
|
||||
export function useHandleCheckableActivityTargetChange({
|
||||
activity,
|
||||
}: {
|
||||
activity?: Pick<Activity, 'id'> & {
|
||||
activityTargets: Array<
|
||||
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
>;
|
||||
};
|
||||
}) {
|
||||
const [addActivityTargetsOnActivity] =
|
||||
useAddActivityTargetsOnActivityMutation({
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
const [removeActivityTargetsOnActivity] =
|
||||
useRemoveActivityTargetsOnActivityMutation({
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
return async function handleCheckItemsChange(
|
||||
entityValues: Record<string, boolean>,
|
||||
entities: CommentableEntityForSelect[],
|
||||
) {
|
||||
if (!activity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentEntityIds = activity.activityTargets.map(
|
||||
({ commentableId }) => commentableId,
|
||||
);
|
||||
|
||||
const entitiesToAdd = entities.filter(
|
||||
({ id }) => entityValues[id] && !currentEntityIds.includes(id),
|
||||
);
|
||||
|
||||
if (entitiesToAdd.length)
|
||||
await addActivityTargetsOnActivity({
|
||||
variables: {
|
||||
activityId: activity.id,
|
||||
activityTargetInputs: entitiesToAdd.map((entity) => ({
|
||||
id: v4(),
|
||||
createdAt: new Date().toISOString(),
|
||||
commentableType: entity.entityType,
|
||||
commentableId: entity.id,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
const activityTargetIdsToDelete = activity.activityTargets
|
||||
.filter(({ commentableId }) => !entityValues[commentableId])
|
||||
.map(({ id }) => id);
|
||||
|
||||
if (activityTargetIdsToDelete.length)
|
||||
await removeActivityTargetsOnActivity({
|
||||
variables: {
|
||||
activityId: activity.id,
|
||||
activityTargetIds: activityTargetIdsToDelete,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { GET_COMPANIES } from '@/companies/queries';
|
||||
import { GET_PEOPLE } from '@/people/queries';
|
||||
import {
|
||||
CommentThread,
|
||||
CommentThreadTarget,
|
||||
useAddCommentThreadTargetsOnCommentThreadMutation,
|
||||
useRemoveCommentThreadTargetsOnCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '../queries';
|
||||
import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect';
|
||||
|
||||
export function useHandleCheckableCommentThreadTargetChange({
|
||||
commentThread,
|
||||
}: {
|
||||
commentThread?: Pick<CommentThread, 'id'> & {
|
||||
commentThreadTargets: Array<
|
||||
Pick<CommentThreadTarget, 'id' | 'commentableId'>
|
||||
>;
|
||||
};
|
||||
}) {
|
||||
const [addCommentThreadTargetsOnCommentThread] =
|
||||
useAddCommentThreadTargetsOnCommentThreadMutation({
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
const [removeCommentThreadTargetsOnCommentThread] =
|
||||
useRemoveCommentThreadTargetsOnCommentThreadMutation({
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
return async function handleCheckItemsChange(
|
||||
entityValues: Record<string, boolean>,
|
||||
entities: CommentableEntityForSelect[],
|
||||
) {
|
||||
if (!commentThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentEntityIds = commentThread.commentThreadTargets.map(
|
||||
({ commentableId }) => commentableId,
|
||||
);
|
||||
|
||||
const entitiesToAdd = entities.filter(
|
||||
({ id }) => entityValues[id] && !currentEntityIds.includes(id),
|
||||
);
|
||||
|
||||
if (entitiesToAdd.length)
|
||||
await addCommentThreadTargetsOnCommentThread({
|
||||
variables: {
|
||||
commentThreadId: commentThread.id,
|
||||
commentThreadTargetInputs: entitiesToAdd.map((entity) => ({
|
||||
id: v4(),
|
||||
createdAt: new Date().toISOString(),
|
||||
commentableType: entity.entityType,
|
||||
commentableId: entity.id,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
const commentThreadTargetIdsToDelete = commentThread.commentThreadTargets
|
||||
.filter(({ commentableId }) => !entityValues[commentableId])
|
||||
.map(({ id }) => id);
|
||||
|
||||
if (commentThreadTargetIdsToDelete.length)
|
||||
await removeCommentThreadTargetsOnCommentThread({
|
||||
variables: {
|
||||
commentThreadId: commentThread.id,
|
||||
commentThreadTargetIds: commentThreadTargetIdsToDelete,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -5,18 +5,16 @@ import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
|
||||
|
||||
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
|
||||
export function useOpenCommentThreadRightDrawer() {
|
||||
export function useOpenActivityRightDrawer() {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const [, setViewableCommentThreadId] = useRecoilState(
|
||||
viewableCommentThreadIdState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
return function openCommentThreadRightDrawer(commentThreadId: string) {
|
||||
return function openActivityRightDrawer(activityId: string) {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableCommentThreadId(commentThreadId);
|
||||
openRightDrawer(RightDrawerPages.EditCommentThread);
|
||||
setViewableActivityId(activityId);
|
||||
openRightDrawer(RightDrawerPages.EditActivity);
|
||||
};
|
||||
}
|
||||
@ -9,40 +9,35 @@ import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
|
||||
import {
|
||||
ActivityType,
|
||||
useCreateCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { ActivityType, useCreateActivityMutation } from '~/generated/graphql';
|
||||
|
||||
import { GET_COMMENT_THREAD, GET_COMMENT_THREADS_BY_TARGETS } from '../queries';
|
||||
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '../queries';
|
||||
import { commentableEntityArrayState } from '../states/commentableEntityArrayState';
|
||||
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
import { CommentableEntity } from '../types/CommentableEntity';
|
||||
|
||||
export function useOpenCreateCommentThreadDrawer() {
|
||||
export function useOpenCreateActivityDrawer() {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const [createCommentThreadMutation] = useCreateCommentThreadMutation();
|
||||
const [createActivityMutation] = useCreateActivityMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const [, setCommentableEntityArray] = useRecoilState(
|
||||
commentableEntityArrayState,
|
||||
);
|
||||
const [, setViewableCommentThreadId] = useRecoilState(
|
||||
viewableCommentThreadIdState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
|
||||
return function openCreateCommentThreadDrawer(
|
||||
return function openCreateActivityDrawer(
|
||||
entity: CommentableEntity,
|
||||
type: ActivityType,
|
||||
) {
|
||||
createCommentThreadMutation({
|
||||
createActivityMutation({
|
||||
variables: {
|
||||
authorId: currentUser?.id ?? '',
|
||||
commentThreadId: v4(),
|
||||
activityId: v4(),
|
||||
createdAt: new Date().toISOString(),
|
||||
type: type,
|
||||
commentThreadTargetArray: [
|
||||
activityTargetArray: [
|
||||
{
|
||||
commentableId: entity.id,
|
||||
commentableType: entity.type,
|
||||
@ -54,14 +49,14 @@ export function useOpenCreateCommentThreadDrawer() {
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
getOperationName(GET_ACTIVITY) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
],
|
||||
onCompleted(data) {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableCommentThreadId(data.createOneCommentThread.id);
|
||||
setViewableActivityId(data.createOneActivity.id);
|
||||
setCommentableEntityArray([entity]);
|
||||
openRightDrawer(RightDrawerPages.CreateCommentThread);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -13,21 +13,19 @@ import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector
|
||||
import {
|
||||
ActivityType,
|
||||
CommentableType,
|
||||
useCreateCommentThreadMutation,
|
||||
useCreateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_COMMENT_THREAD, GET_COMMENT_THREADS_BY_TARGETS } from '../queries';
|
||||
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '../queries';
|
||||
import { commentableEntityArrayState } from '../states/commentableEntityArrayState';
|
||||
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
import { CommentableEntity } from '../types/CommentableEntity';
|
||||
|
||||
export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
||||
export function useOpenCreateActivityDrawerForSelectedRowIds() {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const [createCommentThreadMutation] = useCreateCommentThreadMutation();
|
||||
const [createActivityMutation] = useCreateActivityMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const [, setViewableCommentThreadId] = useRecoilState(
|
||||
viewableCommentThreadIdState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
@ -47,13 +45,13 @@ export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
||||
}),
|
||||
);
|
||||
|
||||
createCommentThreadMutation({
|
||||
createActivityMutation({
|
||||
variables: {
|
||||
authorId: currentUser?.id ?? '',
|
||||
commentThreadId: v4(),
|
||||
activityId: v4(),
|
||||
createdAt: new Date().toISOString(),
|
||||
type: ActivityType.Note,
|
||||
commentThreadTargetArray: commentableEntityArray.map((entity) => ({
|
||||
activityTargetArray: commentableEntityArray.map((entity) => ({
|
||||
commentableId: entity.id,
|
||||
commentableType: entity.type,
|
||||
id: v4(),
|
||||
@ -63,14 +61,14 @@ export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
getOperationName(GET_ACTIVITY) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
],
|
||||
onCompleted(data) {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableCommentThreadId(data.createOneCommentThread.id);
|
||||
setViewableActivityId(data.createOneActivity.id);
|
||||
setCommentableEntityArray(commentableEntityArray);
|
||||
openRightDrawer(RightDrawerPages.CreateCommentThread);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -5,7 +5,7 @@ export const CREATE_COMMENT = gql`
|
||||
$commentId: String!
|
||||
$commentText: String!
|
||||
$authorId: String!
|
||||
$commentThreadId: String!
|
||||
$activityId: String!
|
||||
$createdAt: DateTime!
|
||||
) {
|
||||
createOneComment(
|
||||
@ -14,7 +14,7 @@ export const CREATE_COMMENT = gql`
|
||||
createdAt: $createdAt
|
||||
body: $commentText
|
||||
author: { connect: { id: $authorId } }
|
||||
commentThread: { connect: { id: $commentThreadId } }
|
||||
activity: { connect: { id: $activityId } }
|
||||
}
|
||||
) {
|
||||
id
|
||||
@ -27,32 +27,32 @@ export const CREATE_COMMENT = gql`
|
||||
lastName
|
||||
avatarUrl
|
||||
}
|
||||
commentThreadId
|
||||
activityId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
|
||||
mutation CreateCommentThread(
|
||||
$commentThreadId: String!
|
||||
export const CREATE_ACTIVITY_WITH_COMMENT = gql`
|
||||
mutation CreateActivity(
|
||||
$activityId: String!
|
||||
$body: String
|
||||
$title: String
|
||||
$type: ActivityType!
|
||||
$authorId: String!
|
||||
$createdAt: DateTime!
|
||||
$commentThreadTargetArray: [CommentThreadTargetCreateManyCommentThreadInput!]!
|
||||
$activityTargetArray: [ActivityTargetCreateManyActivityInput!]!
|
||||
) {
|
||||
createOneCommentThread(
|
||||
createOneActivity(
|
||||
data: {
|
||||
id: $commentThreadId
|
||||
id: $activityId
|
||||
createdAt: $createdAt
|
||||
updatedAt: $createdAt
|
||||
author: { connect: { id: $authorId } }
|
||||
body: $body
|
||||
title: $title
|
||||
type: $type
|
||||
commentThreadTargets: {
|
||||
createMany: { data: $commentThreadTargetArray, skipDuplicates: true }
|
||||
activityTargets: {
|
||||
createMany: { data: $activityTargetArray, skipDuplicates: true }
|
||||
}
|
||||
}
|
||||
) {
|
||||
@ -61,11 +61,11 @@ export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
|
||||
updatedAt
|
||||
authorId
|
||||
type
|
||||
commentThreadTargets {
|
||||
activityTargets {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
commentThreadId
|
||||
activityId
|
||||
commentableType
|
||||
commentableId
|
||||
}
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_COMMENT_THREADS_BY_TARGETS = gql`
|
||||
query GetCommentThreadsByTargets(
|
||||
$commentThreadTargetIds: [String!]!
|
||||
$orderBy: [CommentThreadOrderByWithRelationInput!]
|
||||
export const GET_ACTIVITIES_BY_TARGETS = gql`
|
||||
query GetActivitiesByTargets(
|
||||
$activityTargetIds: [String!]!
|
||||
$orderBy: [ActivityOrderByWithRelationInput!]
|
||||
) {
|
||||
findManyCommentThreads(
|
||||
findManyActivities(
|
||||
orderBy: $orderBy
|
||||
where: {
|
||||
commentThreadTargets: {
|
||||
some: { commentableId: { in: $commentThreadTargetIds } }
|
||||
}
|
||||
activityTargets: { some: { commentableId: { in: $activityTargetIds } } }
|
||||
}
|
||||
) {
|
||||
id
|
||||
@ -38,18 +36,18 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql`
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
commentThreadTargets {
|
||||
activityTargets {
|
||||
id
|
||||
commentableId
|
||||
commentableType
|
||||
commentableId
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_COMMENT_THREAD = gql`
|
||||
query GetCommentThread($commentThreadId: String!) {
|
||||
findManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
|
||||
export const GET_ACTIVITY = gql`
|
||||
query GetActivity($activityId: String!) {
|
||||
findManyActivities(where: { id: { equals: $activityId } }) {
|
||||
id
|
||||
createdAt
|
||||
body
|
||||
@ -75,10 +73,10 @@ export const GET_COMMENT_THREAD = gql`
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
commentThreadTargets {
|
||||
activityTargets {
|
||||
id
|
||||
commentableId
|
||||
commentableType
|
||||
commentableId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,18 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const ADD_COMMENT_THREAD_TARGETS = gql`
|
||||
mutation AddCommentThreadTargetsOnCommentThread(
|
||||
$commentThreadId: String!
|
||||
$commentThreadTargetInputs: [CommentThreadTargetCreateManyCommentThreadInput!]!
|
||||
export const ADD_ACTIVITY_TARGETS = gql`
|
||||
mutation AddActivityTargetsOnActivity(
|
||||
$activityId: String!
|
||||
$activityTargetInputs: [ActivityTargetCreateManyActivityInput!]!
|
||||
) {
|
||||
updateOneCommentThread(
|
||||
where: { id: $commentThreadId }
|
||||
data: {
|
||||
commentThreadTargets: {
|
||||
createMany: { data: $commentThreadTargetInputs }
|
||||
}
|
||||
}
|
||||
updateOneActivity(
|
||||
where: { id: $activityId }
|
||||
data: { activityTargets: { createMany: { data: $activityTargetInputs } } }
|
||||
) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
commentThreadTargets {
|
||||
activityTargets {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
@ -27,23 +23,21 @@ export const ADD_COMMENT_THREAD_TARGETS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const REMOVE_COMMENT_THREAD_TARGETS = gql`
|
||||
mutation RemoveCommentThreadTargetsOnCommentThread(
|
||||
$commentThreadId: String!
|
||||
$commentThreadTargetIds: [String!]!
|
||||
export const REMOVE_ACTIVITY_TARGETS = gql`
|
||||
mutation RemoveActivityTargetsOnActivity(
|
||||
$activityId: String!
|
||||
$activityTargetIds: [String!]!
|
||||
) {
|
||||
updateOneCommentThread(
|
||||
where: { id: $commentThreadId }
|
||||
updateOneActivity(
|
||||
where: { id: $activityId }
|
||||
data: {
|
||||
commentThreadTargets: {
|
||||
deleteMany: { id: { in: $commentThreadTargetIds } }
|
||||
}
|
||||
activityTargets: { deleteMany: { id: { in: $activityTargetIds } } }
|
||||
}
|
||||
) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
commentThreadTargets {
|
||||
activityTargets {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
@ -54,23 +48,23 @@ export const REMOVE_COMMENT_THREAD_TARGETS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_COMMENT_THREAD = gql`
|
||||
mutation DeleteCommentThread($commentThreadId: String!) {
|
||||
deleteManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
|
||||
export const DELETE_ACTIVITY = gql`
|
||||
mutation DeleteActivity($activityId: String!) {
|
||||
deleteManyActivities(where: { id: { equals: $activityId } }) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_COMMENT_THREAD = gql`
|
||||
mutation UpdateCommentThread(
|
||||
export const UPDATE_ACTIVITY = gql`
|
||||
mutation UpdateActivity(
|
||||
$id: String!
|
||||
$body: String
|
||||
$title: String
|
||||
$type: ActivityType
|
||||
$completedAt: DateTime
|
||||
) {
|
||||
updateOneCommentThread(
|
||||
updateOneActivity(
|
||||
where: { id: $id }
|
||||
data: {
|
||||
body: $body
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
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_ACTIVITY } 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 {
|
||||
useGetActivityQuery,
|
||||
useUpdateActivityMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { ActivityActionBar } from './ActivityActionBar';
|
||||
|
||||
import '@blocknote/core/style.css';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const StyledUpperPartContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const StyledTopContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: ${({ theme }) =>
|
||||
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px 24px 24px 48px;
|
||||
`;
|
||||
|
||||
const StyledEditableTitleInput = styled.input`
|
||||
background: transparent;
|
||||
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
|
||||
flex-direction: column;
|
||||
font-family: Inter;
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-style: normal;
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
justify-content: center;
|
||||
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
outline: none;
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTopActionsContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
activityId: string;
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
};
|
||||
|
||||
export function Activity({
|
||||
activityId,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
||||
}: OwnProps) {
|
||||
const { data } = useGetActivityQuery({
|
||||
variables: {
|
||||
activityId: activityId ?? '',
|
||||
},
|
||||
skip: !activityId,
|
||||
});
|
||||
const activity = data?.findManyActivities[0];
|
||||
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [title, setTitle] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasUserManuallySetTitle) {
|
||||
setTitle(activity?.title ?? '');
|
||||
}
|
||||
}, [setTitle, activity?.title, hasUserManuallySetTitle]);
|
||||
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
const debounceUpdateTitle = useMemo(() => {
|
||||
function updateTitle(title: string) {
|
||||
if (activity) {
|
||||
updateActivityMutation({
|
||||
variables: {
|
||||
id: activityId,
|
||||
title: title ?? '',
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ACTIVITY) ?? ''],
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateOneActivity: {
|
||||
__typename: 'Activity',
|
||||
id: activityId,
|
||||
title: title,
|
||||
type: activity.type,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return debounce(updateTitle, 200);
|
||||
}, [activityId, updateActivityMutation, activity]);
|
||||
|
||||
function updateTitleFromBody(body: string) {
|
||||
const parsedTitle = JSON.parse(body)[0]?.content[0]?.text;
|
||||
if (!hasUserManuallySetTitle && autoFillTitle) {
|
||||
setTitle(parsedTitle);
|
||||
debounceUpdateTitle(parsedTitle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!activity) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledUpperPartContainer>
|
||||
<StyledTopContainer>
|
||||
<StyledTopActionsContainer>
|
||||
<ActivityTypeDropdown activity={activity} />
|
||||
<ActivityActionBar activityId={activity?.id ?? ''} />
|
||||
</StyledTopActionsContainer>
|
||||
<StyledEditableTitleInput
|
||||
autoFocus
|
||||
placeholder={`${activity.type} title (optional)`}
|
||||
onChange={(event) => {
|
||||
setHasUserManuallySetTitle(true);
|
||||
setTitle(event.target.value);
|
||||
debounceUpdateTitle(event.target.value);
|
||||
}}
|
||||
value={title ?? ''}
|
||||
/>
|
||||
<PropertyBox>
|
||||
<PropertyBoxItem
|
||||
icon={<IconArrowUpRight />}
|
||||
value={
|
||||
<ActivityRelationPicker
|
||||
activity={{
|
||||
id: activity.id,
|
||||
activityTargets: activity.activityTargets ?? [],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Relations"
|
||||
/>
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<ActivityBodyEditor
|
||||
activity={activity}
|
||||
onChange={updateTitleFromBody}
|
||||
/>
|
||||
</StyledUpperPartContainer>
|
||||
{showComment && (
|
||||
<ActivityComments
|
||||
activity={{
|
||||
id: activity.id,
|
||||
comments: activity.comments ?? [],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -3,13 +3,13 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||
import { GET_COMPANIES } from '@/companies/queries';
|
||||
import { GET_PEOPLE } from '@/people/queries';
|
||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { IconTrash } from '@/ui/icon';
|
||||
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
|
||||
import { useDeleteCommentThreadMutation } from '~/generated/graphql';
|
||||
import { useDeleteActivityMutation } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
@ -17,21 +17,21 @@ const StyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
commentThreadId: string;
|
||||
activityId: string;
|
||||
};
|
||||
|
||||
export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
|
||||
export function ActivityActionBar({ activityId }: OwnProps) {
|
||||
const theme = useTheme();
|
||||
const [createCommentMutation] = useDeleteCommentThreadMutation();
|
||||
const [createCommentMutation] = useDeleteActivityMutation();
|
||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
|
||||
function deleteCommentThread() {
|
||||
function deleteActivity() {
|
||||
createCommentMutation({
|
||||
variables: { commentThreadId },
|
||||
variables: { activityId },
|
||||
refetchQueries: [
|
||||
getOperationName(GET_COMPANIES) ?? '',
|
||||
getOperationName(GET_PEOPLE) ?? '',
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||
],
|
||||
});
|
||||
setIsRightDrawerOpen(false);
|
||||
@ -43,7 +43,7 @@ export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
|
||||
icon={
|
||||
<IconTrash size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
||||
}
|
||||
onClick={deleteCommentThread}
|
||||
onClick={deleteActivity}
|
||||
variant={ButtonVariant.Tertiary}
|
||||
/>
|
||||
</StyledContainer>
|
||||
@ -1,34 +0,0 @@
|
||||
import { CommentThreadEditor } from '@/activities/components/CommentThreadEditor';
|
||||
import { useGetCommentThreadQuery } from '~/generated/graphql';
|
||||
|
||||
import '@blocknote/core/style.css';
|
||||
|
||||
type OwnProps = {
|
||||
commentThreadId: string;
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
};
|
||||
|
||||
export function CommentThread({
|
||||
commentThreadId,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
||||
}: OwnProps) {
|
||||
const { data } = useGetCommentThreadQuery({
|
||||
variables: {
|
||||
commentThreadId: commentThreadId ?? '',
|
||||
},
|
||||
skip: !commentThreadId,
|
||||
});
|
||||
const commentThread = data?.findManyCommentThreads[0];
|
||||
|
||||
return commentThread ? (
|
||||
<CommentThreadEditor
|
||||
commentThread={commentThread}
|
||||
showComment={showComment}
|
||||
autoFillTitle={autoFillTitle}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
@ -1,22 +1,22 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { viewableCommentThreadIdState } from '@/activities/states/viewableCommentThreadIdState';
|
||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
||||
import { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
|
||||
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
||||
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
||||
|
||||
import { CommentThread } from '../CommentThread';
|
||||
import { Activity } from '../Activity';
|
||||
|
||||
export function RightDrawerCreateCommentThread() {
|
||||
const commentThreadId = useRecoilValue(viewableCommentThreadIdState);
|
||||
export function RightDrawerCreateActivity() {
|
||||
const activityId = useRecoilValue(viewableActivityIdState);
|
||||
|
||||
return (
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar />
|
||||
<RightDrawerBody>
|
||||
{commentThreadId && (
|
||||
<CommentThread
|
||||
commentThreadId={commentThreadId}
|
||||
{activityId && (
|
||||
<Activity
|
||||
activityId={activityId}
|
||||
showComment={false}
|
||||
autoFillTitle={true}
|
||||
/>
|
||||
@ -1,20 +1,20 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { viewableCommentThreadIdState } from '@/activities/states/viewableCommentThreadIdState';
|
||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
||||
import { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
|
||||
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
||||
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
||||
|
||||
import { CommentThread } from '../CommentThread';
|
||||
import { Activity } from '../Activity';
|
||||
|
||||
export function RightDrawerEditCommentThread() {
|
||||
const commentThreadId = useRecoilValue(viewableCommentThreadIdState);
|
||||
export function RightDrawerEditActivity() {
|
||||
const activityId = useRecoilValue(viewableActivityIdState);
|
||||
|
||||
return (
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar />
|
||||
<RightDrawerBody>
|
||||
{commentThreadId && <CommentThread commentThreadId={commentThreadId} />}
|
||||
{activityId && <Activity activityId={activityId} />}
|
||||
</RightDrawerBody>
|
||||
</RightDrawerPage>
|
||||
);
|
||||
@ -3,6 +3,6 @@ import { atom } from 'recoil';
|
||||
import { CommentableEntity } from '../types/CommentableEntity';
|
||||
|
||||
export const commentableEntityArrayState = atom<CommentableEntity[]>({
|
||||
key: 'comments/commentable-entity-array',
|
||||
key: 'activities/commentable-entity-array',
|
||||
default: [],
|
||||
});
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const viewableActivityIdState = atom<string | null>({
|
||||
key: 'activities/viewable-activity-id',
|
||||
default: null,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const viewableCommentThreadIdState = atom<string | null>({
|
||||
key: 'comments/viewable-comment-thread-id',
|
||||
default: null,
|
||||
});
|
||||
@ -2,16 +2,16 @@ import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CommentThreadCreateButton } from '@/activities/components/CommentThreadCreateButton';
|
||||
import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer';
|
||||
import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton';
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
||||
import { CommentableEntity } from '@/activities/types/CommentableEntity';
|
||||
import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer';
|
||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||
import { IconCircleDot } from '@/ui/icon';
|
||||
import {
|
||||
ActivityType,
|
||||
SortOrder,
|
||||
useGetCommentThreadsByTargetsQuery,
|
||||
useGetActivitiesByTargetsQuery,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { TimelineActivity } from './TimelineActivity';
|
||||
@ -96,9 +96,9 @@ const StyledStartIcon = styled.div`
|
||||
export function Timeline({ entity }: { entity: CommentableEntity }) {
|
||||
const theme = useTheme();
|
||||
|
||||
const { data: queryResult, loading } = useGetCommentThreadsByTargetsQuery({
|
||||
const { data: queryResult, loading } = useGetActivitiesByTargetsQuery({
|
||||
variables: {
|
||||
commentThreadTargetIds: [entity.id],
|
||||
activityTargetIds: [entity.id],
|
||||
orderBy: [
|
||||
{
|
||||
createdAt: SortOrder.Desc,
|
||||
@ -107,21 +107,20 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
||||
},
|
||||
});
|
||||
|
||||
const openCreateCommandThread = useOpenCreateCommentThreadDrawer();
|
||||
const openCreateCommandThread = useOpenCreateActivityDrawer();
|
||||
|
||||
const commentThreads: CommentThreadForDrawer[] =
|
||||
queryResult?.findManyCommentThreads ?? [];
|
||||
const activities: ActivityForDrawer[] = queryResult?.findManyActivities ?? [];
|
||||
|
||||
if (loading) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (!commentThreads.length) {
|
||||
if (!activities.length) {
|
||||
return (
|
||||
<StyledTimelineEmptyContainer>
|
||||
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
|
||||
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle>
|
||||
<CommentThreadCreateButton
|
||||
<ActivityCreateButton
|
||||
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
||||
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
||||
/>
|
||||
@ -132,17 +131,14 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
||||
return (
|
||||
<StyledMainContainer>
|
||||
<StyledTopActionBar>
|
||||
<CommentThreadCreateButton
|
||||
<ActivityCreateButton
|
||||
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
||||
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
||||
/>
|
||||
</StyledTopActionBar>
|
||||
<StyledTimelineContainer>
|
||||
{commentThreads.map((commentThread) => (
|
||||
<TimelineActivity
|
||||
key={commentThread.id}
|
||||
commentThread={commentThread}
|
||||
/>
|
||||
{activities.map((activity) => (
|
||||
<TimelineActivity key={activity.id} activity={activity} />
|
||||
))}
|
||||
<StyledStartIcon>
|
||||
<IconCircleDot size={theme.icon.size.lg} />
|
||||
|
||||
@ -3,14 +3,11 @@ import { Tooltip } from 'react-tooltip';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenCommentThreadRightDrawer';
|
||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||
import { IconNotes } from '@/ui/icon';
|
||||
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||
import {
|
||||
CommentThread,
|
||||
useUpdateCommentThreadMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||
import {
|
||||
beautifyExactDate,
|
||||
beautifyPastDateRelativeToNow,
|
||||
@ -115,35 +112,31 @@ const StyledTimelineItemContainer = styled.div`
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
commentThread: Pick<
|
||||
CommentThread,
|
||||
activity: Pick<
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'createdAt' | 'completedAt' | 'type'
|
||||
> & { author: Pick<CommentThread['author'], 'displayName'> };
|
||||
> & { author: Pick<Activity['author'], 'displayName'> };
|
||||
};
|
||||
|
||||
export function TimelineActivity({ commentThread }: OwnProps) {
|
||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
|
||||
commentThread.createdAt,
|
||||
);
|
||||
const exactCreatedAt = beautifyExactDate(commentThread.createdAt);
|
||||
const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0]?.text;
|
||||
export function TimelineActivity({ activity }: OwnProps) {
|
||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
|
||||
const exactCreatedAt = beautifyExactDate(activity.createdAt);
|
||||
const body = JSON.parse(activity.body ?? '{}')[0]?.content[0]?.text;
|
||||
|
||||
const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer();
|
||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||
|
||||
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) ?? ''],
|
||||
});
|
||||
},
|
||||
[commentThread, updateCommentThreadMutation],
|
||||
[activity, updateActivityMutation],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -153,14 +146,14 @@ export function TimelineActivity({ commentThread }: OwnProps) {
|
||||
<IconNotes />
|
||||
</StyledIconContainer>
|
||||
<StyledItemTitleContainer>
|
||||
<span>{commentThread.author.displayName}</span>
|
||||
created a {commentThread.type.toLowerCase()}
|
||||
<span>{activity.author.displayName}</span>
|
||||
created a note created a {activity.type.toLowerCase()}
|
||||
</StyledItemTitleContainer>
|
||||
<StyledItemTitleDate id={`id-${commentThread.id}`}>
|
||||
<StyledItemTitleDate id={`id-${activity.id}`}>
|
||||
{beautifiedCreatedAt} ago
|
||||
</StyledItemTitleDate>
|
||||
<StyledTooltip
|
||||
anchorSelect={`#id-${commentThread.id}`}
|
||||
anchorSelect={`#id-${activity.id}`}
|
||||
content={exactCreatedAt}
|
||||
clickable
|
||||
noArrow
|
||||
@ -171,13 +164,11 @@ export function TimelineActivity({ commentThread }: OwnProps) {
|
||||
<StyledVerticalLine></StyledVerticalLine>
|
||||
</StyledVerticalLineContainer>
|
||||
<StyledCardContainer>
|
||||
<StyledCard
|
||||
onClick={() => openCommentThreadRightDrawer(commentThread.id)}
|
||||
>
|
||||
<StyledCard onClick={() => openActivityRightDrawer(activity.id)}>
|
||||
<TimelineActivityTitle
|
||||
title={commentThread.title ?? ''}
|
||||
completed={!!commentThread.completedAt}
|
||||
type={commentThread.type}
|
||||
title={activity.title ?? ''}
|
||||
completed={!!activity.completedAt}
|
||||
type={activity.type}
|
||||
onCompletionChange={handleActivityCompletionChange}
|
||||
/>
|
||||
<StyledCardContent>
|
||||
|
||||
4
front/src/modules/activities/types/ActivityForDrawer.ts
Normal file
4
front/src/modules/activities/types/ActivityForDrawer.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { GetActivitiesByTargetsQuery } from '~/generated/graphql';
|
||||
|
||||
export type ActivityForDrawer =
|
||||
GetActivitiesByTargetsQuery['findManyActivities'][0];
|
||||
@ -1,5 +1,3 @@
|
||||
import { CommentThreadForDrawer } from './CommentThreadForDrawer';
|
||||
import { ActivityForDrawer } from './ActivityForDrawer';
|
||||
|
||||
export type CommentForDrawer = NonNullable<
|
||||
CommentThreadForDrawer['comments']
|
||||
>[0];
|
||||
export type CommentForDrawer = NonNullable<ActivityForDrawer['comments']>[0];
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
import { GetCommentThreadsByTargetsQuery } from '~/generated/graphql';
|
||||
|
||||
export type CommentThreadForDrawer =
|
||||
GetCommentThreadsByTargetsQuery['findManyCommentThreads'][0];
|
||||
@ -6,7 +6,7 @@ import { useRecoilState } from 'recoil';
|
||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { CommentThreadTarget } from '~/generated/graphql';
|
||||
import { ActivityTarget } from '~/generated/graphql';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||
|
||||
@ -25,12 +25,12 @@ export function useApolloFactory() {
|
||||
uri: `${process.env.REACT_APP_API_URL}`,
|
||||
cache: new InMemoryCache({
|
||||
typePolicies: {
|
||||
CommentThread: {
|
||||
Activity: {
|
||||
fields: {
|
||||
commentThreadTargets: {
|
||||
activityTargets: {
|
||||
merge(
|
||||
_existing: CommentThreadTarget[] = [],
|
||||
incoming: CommentThreadTarget[],
|
||||
_existing: ActivityTarget[] = [],
|
||||
incoming: ActivityTarget[],
|
||||
) {
|
||||
return [...incoming];
|
||||
},
|
||||
|
||||
@ -14,7 +14,7 @@ import { CompanyChip } from './CompanyChip';
|
||||
type OwnProps = {
|
||||
company: Pick<
|
||||
GetCompaniesQuery['companies'][0],
|
||||
'id' | 'name' | 'domainName' | '_commentThreadCount'
|
||||
'id' | 'name' | 'domainName' | '_activityCount'
|
||||
>;
|
||||
};
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ export const GET_COMPANIES = gql`
|
||||
address
|
||||
linkedinUrl
|
||||
employees
|
||||
_commentThreadCount
|
||||
_activityCount
|
||||
accountOwner {
|
||||
id
|
||||
email
|
||||
|
||||
@ -12,7 +12,7 @@ export const GET_COMPANY = gql`
|
||||
address
|
||||
linkedinUrl
|
||||
employees
|
||||
_commentThreadCount
|
||||
_activityCount
|
||||
accountOwner {
|
||||
id
|
||||
email
|
||||
|
||||
@ -25,7 +25,7 @@ export function EditableCompanyNameCell() {
|
||||
id: currentRowEntityId ?? '',
|
||||
name: name ?? '',
|
||||
domainName: domainName ?? '',
|
||||
_commentThreadCount: commentCount ?? 0,
|
||||
_activityCount: commentCount ?? 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
|
||||
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
export function TableActionBarButtonCreateActivityCompany() {
|
||||
const openCreateActivityRightDrawer =
|
||||
useOpenCreateActivityDrawerForSelectedRowIds();
|
||||
|
||||
async function handleButtonClick() {
|
||||
openCreateActivityRightDrawer(CommentableType.Company);
|
||||
}
|
||||
|
||||
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import { useOpenCreateCommentThreadDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateCommentDrawerForSelectedRowIds';
|
||||
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
export function TableActionBarButtonCreateCommentThreadCompany() {
|
||||
const openCreateCommentThreadRightDrawer =
|
||||
useOpenCreateCommentThreadDrawerForSelectedRowIds();
|
||||
|
||||
async function handleButtonClick() {
|
||||
openCreateCommentThreadRightDrawer(CommentableType.Company);
|
||||
}
|
||||
|
||||
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
|
||||
}
|
||||
@ -10,7 +10,7 @@ type MockedCompany = Pick<
|
||||
| 'address'
|
||||
| 'employees'
|
||||
| 'linkedinUrl'
|
||||
| '_commentThreadCount'
|
||||
| '_activityCount'
|
||||
> & {
|
||||
accountOwner: Pick<
|
||||
User,
|
||||
@ -33,7 +33,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:08:54.724515+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 5000,
|
||||
_commentThreadCount: 0,
|
||||
_activityCount: 0,
|
||||
accountOwner: {
|
||||
email: 'charles@test.com',
|
||||
displayName: 'Charles Test',
|
||||
@ -53,7 +53,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
address: 'Paris, France',
|
||||
employees: 800,
|
||||
_commentThreadCount: 0,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -65,7 +65,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:10:32.530184+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 8000,
|
||||
_commentThreadCount: 0,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -77,7 +77,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-03-21T06:30:25.39474+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 800,
|
||||
_commentThreadCount: 0,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -89,7 +89,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:13:29.712485+00:00',
|
||||
address: 'San Francisco, CA',
|
||||
employees: 400,
|
||||
_commentThreadCount: 0,
|
||||
_activityCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
|
||||
@ -83,10 +83,10 @@ export function useSetCompanyEntityTable() {
|
||||
.getLoadable(companyCommentCountFamilyState(company.id))
|
||||
.valueOrThrow();
|
||||
|
||||
if (currentCommentCount !== company._commentThreadCount) {
|
||||
if (currentCommentCount !== company._activityCount) {
|
||||
set(
|
||||
companyCommentCountFamilyState(company.id),
|
||||
company._commentThreadCount,
|
||||
company._activityCount,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -10,11 +10,7 @@ type OwnProps = {
|
||||
| Partial<
|
||||
Pick<
|
||||
Person,
|
||||
| 'id'
|
||||
| 'firstName'
|
||||
| 'lastName'
|
||||
| 'displayName'
|
||||
| '_commentThreadCount'
|
||||
'id' | 'firstName' | 'lastName' | 'displayName' | '_activityCount'
|
||||
>
|
||||
>
|
||||
| null
|
||||
|
||||
@ -101,12 +101,12 @@ export function useSetPeopleEntityTable() {
|
||||
if (
|
||||
currentNameCell.firstName !== person.firstName ||
|
||||
currentNameCell.lastName !== person.lastName ||
|
||||
currentNameCell.commentCount !== person._commentThreadCount
|
||||
currentNameCell.commentCount !== person._activityCount
|
||||
) {
|
||||
set(peopleNameCellFamilyState(person.id), {
|
||||
firstName: person.firstName ?? null,
|
||||
lastName: person.lastName ?? null,
|
||||
commentCount: person._commentThreadCount,
|
||||
commentCount: person._activityCount,
|
||||
displayName: person.displayName ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export const GET_PEOPLE = gql`
|
||||
jobTitle
|
||||
linkedinUrl
|
||||
createdAt
|
||||
_commentThreadCount
|
||||
_activityCount
|
||||
company {
|
||||
id
|
||||
name
|
||||
@ -107,7 +107,7 @@ export const GET_PERSON_NAMES_AND_COMMENT_COUNT = gql`
|
||||
firstName
|
||||
lastName
|
||||
displayName
|
||||
_commentThreadCount
|
||||
_activityCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -129,7 +129,7 @@ export const GET_PERSON_COMMENT_COUNT = gql`
|
||||
query GetPersonCommentCountById($id: String!) {
|
||||
person: findUniquePerson(id: $id) {
|
||||
id
|
||||
_commentThreadCount
|
||||
_activityCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -15,7 +15,7 @@ export const GET_PERSON = gql`
|
||||
jobTitle
|
||||
linkedinUrl
|
||||
phone
|
||||
_commentThreadCount
|
||||
_activityCount
|
||||
company {
|
||||
id
|
||||
name
|
||||
|
||||
@ -21,7 +21,7 @@ export function EditablePeopleFullNameCell() {
|
||||
<EditablePeopleFullName
|
||||
person={{
|
||||
id: currentRowEntityId ?? undefined,
|
||||
_commentThreadCount: commentCount ?? undefined,
|
||||
_activityCount: commentCount ?? undefined,
|
||||
firstName,
|
||||
lastName,
|
||||
displayName: displayName ?? undefined,
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
|
||||
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
export function TableActionBarButtonCreateActivityPeople() {
|
||||
const openCreateActivityRightDrawer =
|
||||
useOpenCreateActivityDrawerForSelectedRowIds();
|
||||
|
||||
async function handleButtonClick() {
|
||||
openCreateActivityRightDrawer(CommentableType.Person);
|
||||
}
|
||||
|
||||
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import { useOpenCreateCommentThreadDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateCommentDrawerForSelectedRowIds';
|
||||
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
export function TableActionBarButtonCreateCommentThreadPeople() {
|
||||
const openCreateCommentThreadRightDrawer =
|
||||
useOpenCreateCommentThreadDrawerForSelectedRowIds();
|
||||
|
||||
async function handleButtonClick() {
|
||||
openCreateCommentThreadRightDrawer(CommentableType.Person);
|
||||
}
|
||||
|
||||
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
|
||||
}
|
||||
144
front/src/modules/ui/button/components/DropdownButton.tsx
Normal file
144
front/src/modules/ui/button/components/DropdownButton.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconChevronDown } from '@/ui/icon/index';
|
||||
|
||||
type ButtonProps = React.ComponentProps<'button'>;
|
||||
|
||||
export type DropdownOptionType = {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
options: DropdownOptionType[];
|
||||
selectedOptionKey?: string;
|
||||
onSelection: (value: DropdownOptionType) => void;
|
||||
} & ButtonProps;
|
||||
|
||||
const StyledButton = styled.button<ButtonProps & { isOpen: boolean }>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.tertiary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-left-radius: ${({ isOpen, theme }) =>
|
||||
isOpen ? 0 : theme.border.radius.sm};
|
||||
border-bottom-right-radius: ${({ isOpen, theme }) =>
|
||||
isOpen ? 0 : theme.border.radius.sm};
|
||||
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
svg {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 14px;
|
||||
justify-content: center;
|
||||
width: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDropdownItem = styled.button<ButtonProps>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.tertiary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
svg {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 14px;
|
||||
justify-content: center;
|
||||
width: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const DropdownContainer = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const DropdownMenu = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function DropdownButton({
|
||||
options,
|
||||
selectedOptionKey,
|
||||
onSelection,
|
||||
...buttonProps
|
||||
}: OwnProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<
|
||||
DropdownOptionType | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedOptionKey) {
|
||||
const option = options.find((option) => option.key === selectedOptionKey);
|
||||
setSelectedOption(option);
|
||||
} else {
|
||||
setSelectedOption(options[0]);
|
||||
}
|
||||
}, [selectedOptionKey, options]);
|
||||
|
||||
if (!options.length) {
|
||||
throw new Error('You must provide at least one option.');
|
||||
}
|
||||
|
||||
const handleSelect =
|
||||
(option: DropdownOptionType) =>
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
onSelection(option);
|
||||
setSelectedOption(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedOption && (
|
||||
<DropdownContainer>
|
||||
<StyledButton
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
{...buttonProps}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
{selectedOption.icon}
|
||||
{selectedOption.label}
|
||||
{options.length > 1 && <IconChevronDown />}
|
||||
</StyledButton>
|
||||
{isOpen && (
|
||||
<DropdownMenu>
|
||||
{options
|
||||
.filter((option) => option.label !== selectedOption.label)
|
||||
.map((option, index) => (
|
||||
<StyledDropdownItem
|
||||
key={index}
|
||||
onClick={handleSelect(option)}
|
||||
>
|
||||
{option.icon}
|
||||
{option.label}
|
||||
</StyledDropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</DropdownContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { RightDrawerCreateCommentThread } from '@/activities/right-drawer/components/create/RightDrawerCreateCommentThread';
|
||||
import { RightDrawerEditCommentThread } from '@/activities/right-drawer/components/edit/RightDrawerEditCommentThread';
|
||||
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
||||
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
||||
import { RightDrawerTimeline } from '@/activities/right-drawer/components/RightDrawerTimeline';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -18,10 +18,10 @@ export function RightDrawerRouter() {
|
||||
switch (rightDrawerPage) {
|
||||
case RightDrawerPages.Timeline:
|
||||
return <RightDrawerTimeline />;
|
||||
case RightDrawerPages.CreateCommentThread:
|
||||
return <RightDrawerCreateCommentThread />;
|
||||
case RightDrawerPages.EditCommentThread:
|
||||
return <RightDrawerEditCommentThread />;
|
||||
case RightDrawerPages.CreateActivity:
|
||||
return <RightDrawerCreateActivity />;
|
||||
case RightDrawerPages.EditActivity:
|
||||
return <RightDrawerEditActivity />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export enum RightDrawerPages {
|
||||
Timeline = 'timeline',
|
||||
CreateCommentThread = 'create-comment-thread',
|
||||
EditCommentThread = 'edit-comment-thread',
|
||||
CreateActivity = 'create-activity',
|
||||
EditActivity = 'edit-activity',
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ export type EditableChipProps = {
|
||||
value: string;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
ChipComponent: React.ReactNode;
|
||||
commentThreadCount?: number;
|
||||
activityCount?: number;
|
||||
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
rightEndContents?: ReactNode[];
|
||||
onSubmit?: (newValue: string) => void;
|
||||
|
||||
Reference in New Issue
Block a user