Activity cache injection (#3791)
* WIP * Minor fixes * Added TODO * Fix post merge * Fix * Fixed warnings * Fixed comments * Fixed comments * Fixed naming * Removed comment * WIP * WIP 2 * Finished working version * Fixes * Fixed typing * Fixes * Fixes * Fixes * Naming fixes * WIP * Fix import * WIP * Working version on title * Fixed create record id overwrite * Removed unecessary callback * Masterpiece * Fixed delete on click outside drawer or delete * Cleaned * Cleaned * Cleaned * Minor fixes * Fixes * Fixed naming * WIP * Fix * Fixed create from target inline cell * Removed console.log * Fixed delete activity optimistic effect * Fixed no title * Fixed debounce and title body creation --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,13 +1,17 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { useBlockNote } from '@blocknote/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
|
||||
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { FileFolder, useUploadFileMutation } from '~/generated/graphql';
|
||||
@ -23,38 +27,99 @@ const StyledBlockNoteStyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
type ActivityBodyEditorProps = {
|
||||
activity: Pick<Activity, 'id' | 'body'>;
|
||||
onChange?: (activityBody: string) => void;
|
||||
activity: Activity;
|
||||
fillTitleFromBody: boolean;
|
||||
};
|
||||
|
||||
export const ActivityBodyEditor = ({
|
||||
activity,
|
||||
onChange,
|
||||
fillTitleFromBody,
|
||||
}: ActivityBodyEditorProps) => {
|
||||
const [body, setBody] = useState<string | null>(null);
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
const [stringifiedBodyFromEditor, setStringifiedBodyFromEditor] = useState<
|
||||
string | null
|
||||
>(activity.body);
|
||||
|
||||
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
|
||||
activityTitleHasBeenSetFamilyState({
|
||||
activityId: activity.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const modifyActivityFromCache = useModifyRecordFromCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (body) {
|
||||
onChange?.(body);
|
||||
}
|
||||
}, [body, onChange]);
|
||||
const { upsertActivity } = useUpsertActivity();
|
||||
|
||||
const debounceOnChange = useMemo(() => {
|
||||
const onInternalChange = (activityBody: string) => {
|
||||
setBody(activityBody);
|
||||
updateOneRecord?.({
|
||||
idToUpdate: activity.id,
|
||||
updateOneRecordInput: {
|
||||
body: activityBody,
|
||||
const persistBodyDebounced = useDebouncedCallback((newBody: string) => {
|
||||
upsertActivity({
|
||||
activity,
|
||||
input: {
|
||||
body: newBody,
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
const persistTitleAndBodyDebounced = useDebouncedCallback(
|
||||
(newTitle: string, newBody: string) => {
|
||||
upsertActivity({
|
||||
activity,
|
||||
input: {
|
||||
title: newTitle,
|
||||
body: newBody,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return debounce(onInternalChange, 200);
|
||||
}, [updateOneRecord, activity.id]);
|
||||
setActivityTitleHasBeenSet(true);
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const updateTitleAndBody = useCallback(
|
||||
(newStringifiedBody: string) => {
|
||||
const blockBody = JSON.parse(newStringifiedBody);
|
||||
const newTitleFromBody = blockBody[0]?.content?.[0]?.text;
|
||||
|
||||
modifyActivityFromCache(activity.id, {
|
||||
title: () => {
|
||||
return newTitleFromBody;
|
||||
},
|
||||
});
|
||||
|
||||
persistTitleAndBodyDebounced(newTitleFromBody, newStringifiedBody);
|
||||
},
|
||||
[activity.id, modifyActivityFromCache, persistTitleAndBodyDebounced],
|
||||
);
|
||||
|
||||
const handleBodyChange = useCallback(
|
||||
(activityBody: string) => {
|
||||
if (!activityTitleHasBeenSet && fillTitleFromBody) {
|
||||
updateTitleAndBody(activityBody);
|
||||
} else {
|
||||
persistBodyDebounced(activityBody);
|
||||
}
|
||||
},
|
||||
[
|
||||
fillTitleFromBody,
|
||||
persistBodyDebounced,
|
||||
activityTitleHasBeenSet,
|
||||
updateTitleAndBody,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isNonEmptyString(stringifiedBodyFromEditor) &&
|
||||
activity.body !== stringifiedBodyFromEditor
|
||||
) {
|
||||
handleBodyChange(stringifiedBodyFromEditor);
|
||||
}
|
||||
}, [stringifiedBodyFromEditor, handleBodyChange, activity]);
|
||||
|
||||
const slashMenuItems = getSlashMenu();
|
||||
|
||||
@ -85,7 +150,7 @@ export const ActivityBodyEditor = ({
|
||||
: undefined,
|
||||
domAttributes: { editor: { class: 'editor' } },
|
||||
onEditorContentChange: (editor: BlockNoteEditor) => {
|
||||
debounceOnChange(JSON.stringify(editor.topLevelBlocks) ?? '');
|
||||
setStringifiedBodyFromEditor(JSON.stringify(editor.topLevelBlocks) ?? '');
|
||||
},
|
||||
slashMenuItems,
|
||||
blockSpecs: blockSpecs,
|
||||
|
||||
@ -67,6 +67,7 @@ export const ActivityComments = ({
|
||||
|
||||
const { records: comments } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Comment,
|
||||
skip: !isNonEmptyString(activity?.id),
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activity?.id ?? '',
|
||||
|
||||
@ -1,22 +1,26 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
||||
import { ActivityComments } from '@/activities/components/ActivityComments';
|
||||
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
||||
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
|
||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { Comment } from '@/activities/types/Comment';
|
||||
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import {
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { ActivityTitle } from './ActivityTitle';
|
||||
|
||||
@ -54,36 +58,32 @@ const StyledTopContainer = styled.div`
|
||||
`;
|
||||
|
||||
type ActivityEditorProps = {
|
||||
activity: Pick<
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'type' | 'completedAt' | 'dueAt'
|
||||
> & {
|
||||
comments?: Array<Comment> | null;
|
||||
} & {
|
||||
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
|
||||
} & {
|
||||
activityTargets?: Array<
|
||||
Pick<ActivityTarget, 'id' | 'companyId' | 'personId'>
|
||||
> | null;
|
||||
};
|
||||
activity: Activity;
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
fillTitleFromBody?: boolean;
|
||||
};
|
||||
|
||||
export const ActivityEditor = ({
|
||||
activity,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
||||
fillTitleFromBody = false,
|
||||
}: ActivityEditorProps) => {
|
||||
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [title, setTitle] = useState<string | null>(activity.title ?? '');
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { updateOneRecord: updateOneActivity } = useUpdateOneRecord<Activity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener(
|
||||
RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
|
||||
);
|
||||
|
||||
const { upsertActivity } = useUpsertActivity();
|
||||
const { deleteActivityFromCache } = useDeleteActivityFromCache();
|
||||
|
||||
const useUpsertOneActivityMutation: RecordUpdateHook = () => {
|
||||
const upsertActivityMutation = ({ variables }: RecordUpdateHookParams) => {
|
||||
upsertActivity({ activity, input: variables.updateOneRecordInput });
|
||||
};
|
||||
|
||||
return [upsertActivityMutation, { loading: false }];
|
||||
};
|
||||
|
||||
const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
@ -91,6 +91,7 @@ export const ActivityEditor = ({
|
||||
fieldMetadataName: 'dueAt',
|
||||
fieldPosition: 0,
|
||||
clearable: true,
|
||||
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
|
||||
});
|
||||
|
||||
const { FieldContextProvider: AssigneeFieldContextProvider } =
|
||||
@ -100,41 +101,24 @@ export const ActivityEditor = ({
|
||||
fieldMetadataName: 'assignee',
|
||||
fieldPosition: 1,
|
||||
clearable: true,
|
||||
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
|
||||
});
|
||||
|
||||
const updateTitle = useCallback(
|
||||
(newTitle: string) => {
|
||||
updateOneActivity?.({
|
||||
idToUpdate: activity.id,
|
||||
updateOneRecordInput: {
|
||||
title: newTitle ?? '',
|
||||
},
|
||||
});
|
||||
},
|
||||
[activity.id, updateOneActivity],
|
||||
);
|
||||
const handleActivityCompletionChange = useCallback(
|
||||
(value: boolean) => {
|
||||
updateOneActivity?.({
|
||||
idToUpdate: activity.id,
|
||||
updateOneRecordInput: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
});
|
||||
},
|
||||
[activity.id, updateOneActivity],
|
||||
const [isCreatingActivity, setIsCreatingActivity] = useRecoilState(
|
||||
isCreatingActivityState,
|
||||
);
|
||||
|
||||
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||
// TODO: remove
|
||||
|
||||
const updateTitleFromBody = (body: string) => {
|
||||
const blockBody = JSON.parse(body);
|
||||
const parsedTitle = blockBody[0]?.content?.[0]?.text;
|
||||
if (!hasUserManuallySetTitle && autoFillTitle) {
|
||||
setTitle(parsedTitle);
|
||||
debouncedUpdateTitle(parsedTitle);
|
||||
}
|
||||
};
|
||||
useRegisterClickOutsideListenerCallback({
|
||||
callbackId: 'activity-editor',
|
||||
callbackFunction: () => {
|
||||
if (isCreatingActivity) {
|
||||
setIsCreatingActivity(false);
|
||||
deleteActivityFromCache(activity);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!activity) {
|
||||
return <></>;
|
||||
@ -145,17 +129,7 @@ export const ActivityEditor = ({
|
||||
<StyledUpperPartContainer>
|
||||
<StyledTopContainer>
|
||||
<ActivityTypeDropdown activity={activity} />
|
||||
<ActivityTitle
|
||||
title={title ?? ''}
|
||||
completed={!!activity.completedAt}
|
||||
type={activity.type}
|
||||
onTitleChange={(newTitle) => {
|
||||
setTitle(newTitle);
|
||||
setHasUserManuallySetTitle(true);
|
||||
debouncedUpdateTitle(newTitle);
|
||||
}}
|
||||
onCompletionChange={handleActivityCompletionChange}
|
||||
/>
|
||||
<ActivityTitle activity={activity} />
|
||||
<PropertyBox>
|
||||
{activity.type === 'Task' &&
|
||||
DueAtFieldContextProvider &&
|
||||
@ -169,14 +143,12 @@ export const ActivityEditor = ({
|
||||
</AssigneeFieldContextProvider>
|
||||
</>
|
||||
)}
|
||||
<ActivityTargetsInlineCell
|
||||
activity={activity as unknown as GraphQLActivity}
|
||||
/>
|
||||
<ActivityTargetsInlineCell activity={activity} />
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<ActivityBodyEditor
|
||||
activity={activity}
|
||||
onChange={updateTitleFromBody}
|
||||
fillTitleFromBody={fillTitleFromBody}
|
||||
/>
|
||||
</StyledUpperPartContainer>
|
||||
{showComment && (
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxShape,
|
||||
CheckboxSize,
|
||||
} from '@/ui/input/components/Checkbox';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledEditableTitleInput = styled.input<{
|
||||
completed: boolean;
|
||||
@ -39,36 +48,83 @@ const StyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
type ActivityTitleProps = {
|
||||
title: string;
|
||||
type: ActivityType;
|
||||
completed: boolean;
|
||||
onTitleChange: (title: string) => void;
|
||||
onCompletionChange: (value: boolean) => void;
|
||||
activity: Activity;
|
||||
};
|
||||
|
||||
export const ActivityTitle = ({
|
||||
title,
|
||||
completed,
|
||||
type,
|
||||
onTitleChange,
|
||||
onCompletionChange,
|
||||
}: ActivityTitleProps) => (
|
||||
<StyledContainer>
|
||||
{type === 'Task' && (
|
||||
<Checkbox
|
||||
size={CheckboxSize.Large}
|
||||
shape={CheckboxShape.Rounded}
|
||||
checked={completed}
|
||||
onCheckedChange={(value) => onCompletionChange(value)}
|
||||
export const ActivityTitle = ({ activity }: ActivityTitleProps) => {
|
||||
const [internalTitle, setInternalTitle] = useState(activity.title);
|
||||
|
||||
const { upsertActivity } = useUpsertActivity();
|
||||
|
||||
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
|
||||
activityTitleHasBeenSetFamilyState({
|
||||
activityId: activity.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const modifyActivityFromCache = useModifyRecordFromCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const persistTitleDebounced = useDebouncedCallback((newTitle: string) => {
|
||||
upsertActivity({
|
||||
activity,
|
||||
input: {
|
||||
title: newTitle,
|
||||
},
|
||||
});
|
||||
|
||||
if (!activityTitleHasBeenSet) {
|
||||
setActivityTitleHasBeenSet(true);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
const handleTitleChange = (newTitle: string) => {
|
||||
setInternalTitle(newTitle);
|
||||
|
||||
modifyActivityFromCache(activity.id, {
|
||||
title: () => {
|
||||
return newTitle;
|
||||
},
|
||||
});
|
||||
|
||||
persistTitleDebounced(newTitle);
|
||||
};
|
||||
|
||||
const handleActivityCompletionChange = (value: boolean) => {
|
||||
upsertActivity({
|
||||
activity,
|
||||
input: {
|
||||
completedAt: value ? new Date().toISOString() : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const completed = isDefined(activity.completedAt);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{activity.type === 'Task' && (
|
||||
<Checkbox
|
||||
size={CheckboxSize.Large}
|
||||
shape={CheckboxShape.Rounded}
|
||||
checked={completed}
|
||||
onCheckedChange={(value) => handleActivityCompletionChange(value)}
|
||||
/>
|
||||
)}
|
||||
<StyledEditableTitleInput
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
placeholder={`${activity.type} title`}
|
||||
onChange={(event) => handleTitleChange(event.target.value)}
|
||||
value={internalTitle}
|
||||
completed={completed}
|
||||
/>
|
||||
)}
|
||||
<StyledEditableTitleInput
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
placeholder={`${type} title`}
|
||||
onChange={(event) => onTitleChange(event.target.value)}
|
||||
value={title}
|
||||
completed={completed}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
|
||||
const QUERY_DEPTH_TO_GET_ACTIVITY_TARGET_RELATIONS = 3;
|
||||
|
||||
export const useActivityById = ({ activityId }: { activityId: string }) => {
|
||||
const setEntityFields = useSetRecoilState(recordStoreFamilyState(activityId));
|
||||
|
||||
const { makeActivityWithoutConnection } = useActivityConnectionUtils();
|
||||
|
||||
const { record: activityWithConnections } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
objectRecordId: activityId,
|
||||
skip: !activityId,
|
||||
onCompleted: (activityWithConnections: any) => {
|
||||
const { activity } = makeActivityWithoutConnection(
|
||||
activityWithConnections,
|
||||
);
|
||||
|
||||
setEntityFields(activity);
|
||||
},
|
||||
depth: QUERY_DEPTH_TO_GET_ACTIVITY_TARGET_RELATIONS,
|
||||
});
|
||||
|
||||
const { activity } = makeActivityWithoutConnection(activityWithConnections);
|
||||
|
||||
return {
|
||||
activity,
|
||||
};
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActivityTargetObjectRecord } from '@/activities/types/ActivityTargetObject';
|
||||
@ -17,6 +18,7 @@ export const useActivityTargetObjectRecords = ({
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
skip: !isNonEmptyString(activityId),
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activityId,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
@ -6,7 +7,7 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTarget
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
|
||||
export const useActivityTargets = ({
|
||||
export const useActivityTargetsForTargetableObject = ({
|
||||
targetableObject,
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
@ -17,9 +18,14 @@ export const useActivityTargets = ({
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const targetableObjectId = targetableObject.id;
|
||||
|
||||
const skipRequest = !isNonEmptyString(targetableObjectId);
|
||||
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
skip: skipRequest,
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
@ -0,0 +1,91 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { StringKeyOf } from 'type-fest';
|
||||
|
||||
import { getRelationDefinition } from '@/apollo/optimistic-effect/utils/getRelationDefinition';
|
||||
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getObjectMetadataItemByNameSingular } from '@/object-metadata/utils/getObjectMetadataItemBySingularName';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useAttachRelationInBothDirections = () => {
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const attachRelationInBothDirections = <
|
||||
Source extends ObjectRecord = ObjectRecord,
|
||||
Target extends ObjectRecord = ObjectRecord,
|
||||
>({
|
||||
sourceRecord,
|
||||
targetRecords,
|
||||
sourceObjectNameSingular,
|
||||
targetObjectNameSingular,
|
||||
fieldNameOnSourceRecord,
|
||||
fieldNameOnTargetRecord,
|
||||
}: {
|
||||
sourceRecord: Source;
|
||||
targetRecords: Target[];
|
||||
sourceObjectNameSingular: string;
|
||||
targetObjectNameSingular: string;
|
||||
fieldNameOnSourceRecord: StringKeyOf<Source>;
|
||||
fieldNameOnTargetRecord: StringKeyOf<Target>;
|
||||
}) => {
|
||||
const sourceObjectMetadataItem = getObjectMetadataItemByNameSingular({
|
||||
objectMetadataItems,
|
||||
objectNameSingular: sourceObjectNameSingular,
|
||||
});
|
||||
|
||||
const targetObjectMetadataItem = getObjectMetadataItemByNameSingular({
|
||||
objectMetadataItems,
|
||||
objectNameSingular: targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const fieldMetadataItemOnSourceRecord =
|
||||
sourceObjectMetadataItem.fields.find(
|
||||
(field) => field.name === fieldNameOnSourceRecord,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItemOnSourceRecord)) {
|
||||
throw new Error(
|
||||
`Field ${fieldNameOnSourceRecord} not found on object ${sourceObjectNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationDefinition = getRelationDefinition({
|
||||
fieldMetadataItemOnSourceRecord: fieldMetadataItemOnSourceRecord,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
if (!isDefined(relationDefinition)) {
|
||||
throw new Error(
|
||||
`Relation metadata not found for field ${fieldNameOnSourceRecord} on object ${sourceObjectNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: could we use triggerUpdateRelationsOptimisticEffect here?
|
||||
targetRecords.forEach((relationTargetRecord) => {
|
||||
triggerAttachRelationOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
|
||||
sourceRecordId: sourceRecord.id,
|
||||
fieldNameOnTargetRecord: fieldNameOnTargetRecord,
|
||||
targetObjectNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
targetRecordId: relationTargetRecord.id,
|
||||
});
|
||||
|
||||
triggerAttachRelationOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
sourceObjectNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
sourceRecordId: relationTargetRecord.id,
|
||||
fieldNameOnTargetRecord: fieldNameOnSourceRecord,
|
||||
targetObjectNameSingular: sourceObjectMetadataItem.nameSingular,
|
||||
targetRecordId: sourceRecord.id,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
attachRelationInBothDirections,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,115 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useAttachRelationInBothDirections } from '@/activities/hooks/useAttachRelationInBothDirections';
|
||||
import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-cell/hooks/useInjectIntoActivityTargetInlineCellCache';
|
||||
import { useInjectIntoTimelineActivitiesQueryAfterDrawerMount } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQueryAfterDrawerMount';
|
||||
import { Activity, ActivityType } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetsToCreateFromTargetableObjects } from '@/activities/utils/getActivityTargetsToCreateFromTargetableObjects';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateManyRecordsInCache } from '@/object-record/hooks/useCreateManyRecordsInCache';
|
||||
import { useCreateOneRecordInCache } from '@/object-record/hooks/useCreateOneRecordInCache';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
|
||||
export const useCreateActivityInCache = () => {
|
||||
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
|
||||
useCreateManyRecordsInCache<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const { createOneRecordInCache: createOneActivityInCache } =
|
||||
useCreateOneRecordInCache<Activity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { record: workspaceMemberRecord } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
objectRecordId: currentWorkspaceMember?.id,
|
||||
depth: 3,
|
||||
});
|
||||
|
||||
const { injectIntoTimelineActivitiesQueryAfterDrawerMount } =
|
||||
useInjectIntoTimelineActivitiesQueryAfterDrawerMount();
|
||||
|
||||
const { injectIntoActivityTargetInlineCellCache } =
|
||||
useInjectIntoActivityTargetInlineCellCache();
|
||||
|
||||
const {
|
||||
attachRelationInBothDirections:
|
||||
attachRelationSourceRecordToItsRelationTargetRecordsAndViceVersaInCache,
|
||||
} = useAttachRelationInBothDirections();
|
||||
|
||||
const createActivityInCache = ({
|
||||
type,
|
||||
targetableObjects,
|
||||
timelineTargetableObject,
|
||||
assigneeId,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
timelineTargetableObject: ActivityTargetableObject;
|
||||
assigneeId?: string;
|
||||
}) => {
|
||||
const activityId = v4();
|
||||
|
||||
const createdActivityInCache = createOneActivityInCache({
|
||||
id: activityId,
|
||||
author: workspaceMemberRecord,
|
||||
authorId: workspaceMemberRecord?.id,
|
||||
assignee: !assigneeId ? workspaceMemberRecord : undefined,
|
||||
assigneeId:
|
||||
assigneeId ?? isNonEmptyString(workspaceMemberRecord?.id)
|
||||
? workspaceMemberRecord?.id
|
||||
: undefined,
|
||||
type: type,
|
||||
});
|
||||
|
||||
const activityTargetsToCreate =
|
||||
getActivityTargetsToCreateFromTargetableObjects({
|
||||
activityId,
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const createdActivityTargetsInCache = createManyActivityTargetsInCache(
|
||||
activityTargetsToCreate,
|
||||
);
|
||||
|
||||
injectIntoTimelineActivitiesQueryAfterDrawerMount({
|
||||
activityToInject: createdActivityInCache,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
timelineTargetableObject,
|
||||
});
|
||||
|
||||
injectIntoActivityTargetInlineCellCache({
|
||||
activityId,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
attachRelationSourceRecordToItsRelationTargetRecordsAndViceVersaInCache({
|
||||
sourceRecord: createdActivityInCache,
|
||||
fieldNameOnSourceRecord: 'activityTargets',
|
||||
sourceObjectNameSingular: CoreObjectNameSingular.Activity,
|
||||
fieldNameOnTargetRecord: 'activity',
|
||||
targetObjectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
targetRecords: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
return {
|
||||
createdActivityInCache: {
|
||||
...createdActivityInCache,
|
||||
activityTargets: createdActivityTargetsInCache,
|
||||
},
|
||||
createdActivityTargetsInCache,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
createActivityInCache,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
|
||||
import { useModifyActivityTargetsOnActivityCache } from '@/activities/hooks/useModifyActivityTargetsOnActivityCache';
|
||||
import { ActivityForEditor } from '@/activities/types/ActivityForEditor';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
|
||||
export const useCreateActivityInDB = () => {
|
||||
const { createOneRecord: createOneActivity } = useCreateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const { createManyRecords: createManyActivityTargets } =
|
||||
useCreateManyRecords<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const { makeActivityWithConnection } = useActivityConnectionUtils();
|
||||
|
||||
const { modifyActivityTargetsOnActivityCache } =
|
||||
useModifyActivityTargetsOnActivityCache();
|
||||
|
||||
const createActivityInDB = async (activityToCreate: ActivityForEditor) => {
|
||||
const { activityWithConnection } = makeActivityWithConnection(
|
||||
activityToCreate as any, // TODO: fix type
|
||||
);
|
||||
|
||||
await createOneActivity?.(
|
||||
{
|
||||
...activityWithConnection,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
skipOptimisticEffect: true,
|
||||
},
|
||||
);
|
||||
|
||||
const activityTargetsToCreate = activityToCreate.activityTargets ?? [];
|
||||
|
||||
if (isNonEmptyArray(activityTargetsToCreate)) {
|
||||
await createManyActivityTargets(activityTargetsToCreate, {
|
||||
skipOptimisticEffect: true,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: replace by trigger optimistic effect
|
||||
modifyActivityTargetsOnActivityCache({
|
||||
activityId: activityToCreate.id,
|
||||
activityTargets: activityTargetsToCreate,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createActivityInDB,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { ActivityForEditor } from '@/activities/types/ActivityForEditor';
|
||||
import { useActivityConnectionUtils } from '@/activities/utils/useActivityConnectionUtils';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
|
||||
// TODO: this should be useDeleteRecordFromCache
|
||||
export const useDeleteActivityFromCache = () => {
|
||||
const { makeActivityWithConnection } = useActivityConnectionUtils();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const deleteActivityFromCache = (activityToDelete: ActivityForEditor) => {
|
||||
const { activityWithConnection } = makeActivityWithConnection(
|
||||
activityToDelete as any, // TODO: fix type
|
||||
);
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
objectMetadataItems,
|
||||
recordsToDelete: [activityWithConnection],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
deleteActivityFromCache,
|
||||
};
|
||||
};
|
||||
@ -1,163 +1,61 @@
|
||||
import { useCallback } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
|
||||
import { useModifyActivityOnActivityTargetsCache } from '@/activities/hooks/useModifyActivityOnActivityTargetCache';
|
||||
import { useModifyActivityTargetsOnActivityCache } from '@/activities/hooks/useModifyActivityTargetsOnActivityCache';
|
||||
import { useWriteActivityTargetsInCache } from '@/activities/hooks/useWriteActivityTargetsInCache';
|
||||
import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-cell/hooks/useInjectIntoActivityTargetInlineCellCache';
|
||||
import { useInjectIntoTimelineActivitiesQuery } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQuery';
|
||||
import { Activity, ActivityType } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { getActivityTargetsToCreateFromTargetableObjects } from '@/activities/utils/getActivityTargetsToCreateFromTargetableObjects';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateManyRecordsInCache } from '@/object-record/hooks/useCreateManyRecordsInCache';
|
||||
import { useCreateOneRecordInCache } from '@/object-record/hooks/useCreateOneRecordInCache';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
|
||||
import { useCreateActivityInCache } from '@/activities/hooks/useCreateActivityInCache';
|
||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
|
||||
import { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState';
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
export const useOpenCreateActivityDrawerV2 = ({
|
||||
targetableObject,
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
export const useOpenCreateActivityDrawerV2 = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
|
||||
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
|
||||
useCreateManyRecordsInCache<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const { createOneRecordInCache: createOneActivityInCache } =
|
||||
useCreateOneRecordInCache<Activity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { record: workspaceMemberRecord } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
objectRecordId: currentWorkspaceMember?.id,
|
||||
});
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { createActivityInCache } = useCreateActivityInCache();
|
||||
|
||||
const [, setActivityTargetableEntityArray] = useRecoilState(
|
||||
activityTargetableEntityArrayState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
|
||||
const { activityTargets } = useActivityTargets({
|
||||
targetableObject,
|
||||
});
|
||||
const setIsCreatingActivity = useSetRecoilState(isCreatingActivityState);
|
||||
|
||||
const { injectIntoTimelineActivitiesNextQuery } =
|
||||
useInjectIntoTimelineActivitiesQuery();
|
||||
const setTemporaryActivityForEditor = useSetRecoilState(
|
||||
temporaryActivityForEditorState,
|
||||
);
|
||||
|
||||
const { injectIntoActivityTargetInlineCellCache } =
|
||||
useInjectIntoActivityTargetInlineCellCache();
|
||||
|
||||
const { injectIntoUseActivityTargets } = useWriteActivityTargetsInCache();
|
||||
|
||||
const { modifyActivityTargetsOnActivityCache } =
|
||||
useModifyActivityTargetsOnActivityCache();
|
||||
|
||||
const { modifyActivityOnActivityTargetsCache } =
|
||||
useModifyActivityOnActivityTargetsCache();
|
||||
|
||||
return useCallback(
|
||||
async ({
|
||||
const openCreateActivityDrawer = async ({
|
||||
type,
|
||||
targetableObjects,
|
||||
timelineTargetableObject,
|
||||
assigneeId,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
timelineTargetableObject: ActivityTargetableObject;
|
||||
assigneeId?: string;
|
||||
}) => {
|
||||
const { createdActivityInCache } = createActivityInCache({
|
||||
type,
|
||||
targetableObjects,
|
||||
timelineTargetableObject,
|
||||
assigneeId,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
assigneeId?: string;
|
||||
}) => {
|
||||
const activityId = v4();
|
||||
});
|
||||
|
||||
const createdActivityInCache = await createOneActivityInCache({
|
||||
id: activityId,
|
||||
author: workspaceMemberRecord,
|
||||
authorId: workspaceMemberRecord?.id,
|
||||
assignee: !assigneeId ? workspaceMemberRecord : undefined,
|
||||
assigneeId:
|
||||
assigneeId ?? isNonEmptyString(workspaceMemberRecord?.id)
|
||||
? workspaceMemberRecord?.id
|
||||
: undefined,
|
||||
type: type,
|
||||
});
|
||||
setTemporaryActivityForEditor(createdActivityInCache);
|
||||
setIsCreatingActivity(true);
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(createdActivityInCache.id);
|
||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
};
|
||||
|
||||
if (!createdActivityInCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activityTargetsToCreate =
|
||||
getActivityTargetsToCreateFromTargetableObjects({
|
||||
activityId,
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const createdActivityTargetsInCache =
|
||||
await createManyActivityTargetsInCache(activityTargetsToCreate);
|
||||
|
||||
injectIntoUseActivityTargets({
|
||||
targetableObject,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
injectIntoTimelineActivitiesNextQuery({
|
||||
activityTargets,
|
||||
activityToInject: createdActivityInCache,
|
||||
});
|
||||
|
||||
injectIntoActivityTargetInlineCellCache({
|
||||
activityId,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
modifyActivityTargetsOnActivityCache({
|
||||
activityId,
|
||||
activityTargets: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
modifyActivityOnActivityTargetsCache({
|
||||
activityTargetIds: createdActivityTargetsInCache.map(mapToRecordId),
|
||||
activity: createdActivityInCache,
|
||||
});
|
||||
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(activityId);
|
||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
},
|
||||
[
|
||||
openRightDrawer,
|
||||
setActivityTargetableEntityArray,
|
||||
createManyActivityTargetsInCache,
|
||||
setHotkeyScope,
|
||||
setViewableActivityId,
|
||||
createOneActivityInCache,
|
||||
workspaceMemberRecord,
|
||||
activityTargets,
|
||||
targetableObject,
|
||||
injectIntoTimelineActivitiesNextQuery,
|
||||
injectIntoActivityTargetInlineCellCache,
|
||||
injectIntoUseActivityTargets,
|
||||
modifyActivityTargetsOnActivityCache,
|
||||
modifyActivityOnActivityTargetsCache,
|
||||
],
|
||||
);
|
||||
return openCreateActivityDrawer;
|
||||
};
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
|
||||
export const useUpsertActivity = () => {
|
||||
const [isCreatingActivity, setIsCreatingActivity] = useRecoilState(
|
||||
isCreatingActivityState,
|
||||
);
|
||||
|
||||
const { updateOneRecord: updateOneActivity } = useUpdateOneRecord<Activity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const { createActivityInDB } = useCreateActivityInDB();
|
||||
|
||||
const upsertActivity = ({
|
||||
activity,
|
||||
input,
|
||||
}: {
|
||||
activity: Activity;
|
||||
input: Partial<Activity>;
|
||||
}) => {
|
||||
if (isCreatingActivity) {
|
||||
createActivityInDB({
|
||||
...activity,
|
||||
...input,
|
||||
});
|
||||
|
||||
setIsCreatingActivity(false);
|
||||
} else {
|
||||
updateOneActivity?.({
|
||||
idToUpdate: activity.id,
|
||||
updateOneRecordInput: input,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
upsertActivity,
|
||||
};
|
||||
};
|
||||
@ -1,84 +0,0 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const useWriteActivityTargetsInCache = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const {
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
findManyRecordsQuery: findManyActivityTargetsQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const injectIntoUseActivityTargets = ({
|
||||
targetableObject,
|
||||
activityTargetsToInject,
|
||||
}: {
|
||||
targetableObject: Pick<
|
||||
ActivityTargetableObject,
|
||||
'id' | 'targetObjectNameSingular'
|
||||
>;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
}) => {
|
||||
const targetObjectFieldName = getActivityTargetObjectFieldIdName({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const existingActivityTargetsForTargetableObjectQueryResult =
|
||||
apolloClient.readQuery({
|
||||
query: findManyActivityTargetsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const existingActivityTargetsForTargetableObject =
|
||||
getRecordsFromRecordConnection({
|
||||
recordConnection: existingActivityTargetsForTargetableObjectQueryResult[
|
||||
objectMetadataItemActivityTarget.namePlural
|
||||
] as ObjectRecordConnection<ActivityTarget>,
|
||||
});
|
||||
|
||||
const newActivityTargetsForTargetableObject = [
|
||||
...existingActivityTargetsForTargetableObject,
|
||||
...activityTargetsToInject,
|
||||
];
|
||||
|
||||
const newActivityTargetsConnection = getRecordConnectionFromRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
records: newActivityTargetsForTargetableObject,
|
||||
});
|
||||
|
||||
apolloClient.writeQuery({
|
||||
query: findManyActivityTargetsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
[objectMetadataItemActivityTarget.namePlural]:
|
||||
newActivityTargetsConnection,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectIntoUseActivityTargets,
|
||||
};
|
||||
};
|
||||
@ -1,10 +1,18 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||
import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-cell/hooks/useInjectIntoActivityTargetInlineCellCache';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetObjectRecord } from '@/activities/types/ActivityTargetObject';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
|
||||
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
@ -18,14 +26,16 @@ const StyledSelectContainer = styled.div`
|
||||
`;
|
||||
|
||||
type ActivityTargetInlineCellEditModeProps = {
|
||||
activityId: string;
|
||||
activity: Activity;
|
||||
activityTargetObjectRecords: ActivityTargetObjectRecord[];
|
||||
};
|
||||
|
||||
export const ActivityTargetInlineCellEditMode = ({
|
||||
activityId,
|
||||
activity,
|
||||
activityTargetObjectRecords,
|
||||
}: ActivityTargetInlineCellEditModeProps) => {
|
||||
const [isCreatingActivity] = useRecoilState(isCreatingActivityState);
|
||||
|
||||
const selectedObjectRecordIds = activityTargetObjectRecords.map(
|
||||
(activityTarget) => ({
|
||||
objectNameSingular: activityTarget.targetObjectNameSingular,
|
||||
@ -46,6 +56,21 @@ export const ActivityTargetInlineCellEditMode = ({
|
||||
|
||||
const { closeInlineCell: closeEditableField } = useInlineCell();
|
||||
|
||||
const { upsertActivity } = useUpsertActivity();
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const { injectIntoActivityTargetInlineCellCache } =
|
||||
useInjectIntoActivityTargetInlineCellCache();
|
||||
|
||||
const { generateObjectRecordOptimisticResponse } =
|
||||
useGenerateObjectRecordOptimisticResponse({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const handleSubmit = async (selectedRecords: ObjectRecordForSelect[]) => {
|
||||
closeEditableField();
|
||||
|
||||
@ -67,25 +92,74 @@ export const ActivityTargetInlineCellEditMode = ({
|
||||
),
|
||||
);
|
||||
|
||||
if (activityTargetRecordsToCreate.length > 0) {
|
||||
await createManyActivityTargets(
|
||||
activityTargetRecordsToCreate.map((selectedRecord) => ({
|
||||
id: v4(),
|
||||
activityId,
|
||||
[getActivityTargetObjectFieldIdName({
|
||||
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
|
||||
})]: selectedRecord.recordIdentifier.id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
if (isCreatingActivity) {
|
||||
let activityTargetsForCreation = activity.activityTargets;
|
||||
|
||||
if (activityTargetRecordsToDelete.length > 0) {
|
||||
await deleteManyActivityTargets(
|
||||
activityTargetRecordsToDelete.map(
|
||||
(activityTargetObjectRecord) =>
|
||||
activityTargetObjectRecord.activityTargetRecord.id,
|
||||
),
|
||||
);
|
||||
if (isNonEmptyArray(activityTargetsForCreation)) {
|
||||
const generatedActivityTargets = activityTargetRecordsToCreate.map(
|
||||
(selectedRecord) => {
|
||||
const emptyActivityTarget =
|
||||
generateObjectRecordOptimisticResponse<ActivityTarget>({
|
||||
id: v4(),
|
||||
activityId: activity.id,
|
||||
activity,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
[getActivityTargetObjectFieldIdName({
|
||||
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
|
||||
})]: selectedRecord.recordIdentifier.id,
|
||||
});
|
||||
|
||||
return emptyActivityTarget;
|
||||
},
|
||||
);
|
||||
|
||||
activityTargetsForCreation.push(...generatedActivityTargets);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(activityTargetRecordsToDelete)) {
|
||||
activityTargetsForCreation = activityTargetsForCreation.filter(
|
||||
(activityTarget) =>
|
||||
!activityTargetRecordsToDelete.some(
|
||||
(activityTargetObjectRecord) =>
|
||||
activityTargetObjectRecord.targetObjectRecord.id ===
|
||||
activityTarget.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
injectIntoActivityTargetInlineCellCache({
|
||||
activityId: activity.id,
|
||||
activityTargetsToInject: activityTargetsForCreation,
|
||||
});
|
||||
|
||||
upsertActivity({
|
||||
activity,
|
||||
input: {
|
||||
activityTargets: activityTargetsForCreation,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (activityTargetRecordsToCreate.length > 0) {
|
||||
await createManyActivityTargets(
|
||||
activityTargetRecordsToCreate.map((selectedRecord) => ({
|
||||
id: v4(),
|
||||
activityId: activity.id,
|
||||
[getActivityTargetObjectFieldIdName({
|
||||
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
|
||||
})]: selectedRecord.recordIdentifier.id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (activityTargetRecordsToDelete.length > 0) {
|
||||
await deleteManyActivityTargets(
|
||||
activityTargetRecordsToDelete.map(
|
||||
(activityTargetObjectRecord) =>
|
||||
activityTargetObjectRecord.activityTargetRecord.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
|
||||
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
||||
import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
|
||||
@ -11,13 +10,7 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
|
||||
import { IconArrowUpRight, IconPencil } from '@/ui/display/icon';
|
||||
|
||||
type ActivityTargetsInlineCellProps = {
|
||||
activity?: Pick<GraphQLActivity, 'id'> & {
|
||||
activityTargets?: {
|
||||
edges: Array<{
|
||||
node: Pick<ActivityTarget, 'id'>;
|
||||
}> | null;
|
||||
};
|
||||
};
|
||||
activity: Activity;
|
||||
};
|
||||
|
||||
export const ActivityTargetsInlineCell = ({
|
||||
@ -47,8 +40,8 @@ export const ActivityTargetsInlineCell = ({
|
||||
IconLabel={IconArrowUpRight}
|
||||
editModeContent={
|
||||
<ActivityTargetInlineCellEditMode
|
||||
activityId={activity?.id ?? ''}
|
||||
activityTargetObjectRecords={activityTargetObjectRecords as any}
|
||||
activity={activity}
|
||||
activityTargetObjectRecords={activityTargetObjectRecords}
|
||||
/>
|
||||
}
|
||||
label="Relations"
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getRecordConnectionFromEdges } from '@/object-record/cache/utils/getRecordConnectionFromEdges';
|
||||
import { getRecordEdgeFromRecord } from '@/object-record/cache/utils/getRecordEdgeFromRecord';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
|
||||
export const useInjectIntoActivityTargetInlineCellCache = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache:
|
||||
overwriteFindManyActivityTargetsQueryInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
findManyRecordsQuery: findManyActivityTargetsQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const injectIntoActivityTargetInlineCellCache = ({
|
||||
@ -23,32 +23,17 @@ export const useInjectIntoActivityTargetInlineCellCache = () => {
|
||||
activityId: string;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
}) => {
|
||||
const newActivityTargetEdgesForCache = activityTargetsToInject.map(
|
||||
(activityTargetToInject) =>
|
||||
getRecordEdgeFromRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
record: activityTargetToInject,
|
||||
}),
|
||||
);
|
||||
|
||||
const newActivityTargetConnectionForCache = getRecordConnectionFromEdges({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
edges: newActivityTargetEdgesForCache,
|
||||
});
|
||||
|
||||
apolloClient.writeQuery({
|
||||
query: findManyActivityTargetsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activityId,
|
||||
},
|
||||
const activityTargetInlineCellQueryVariables = {
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activityId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
[objectMetadataItemActivityTarget.namePlural]:
|
||||
newActivityTargetConnectionForCache,
|
||||
},
|
||||
};
|
||||
|
||||
overwriteFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: activityTargetInlineCellQueryVariables,
|
||||
objectRecordsToOverwrite: activityTargetsToInject,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
|
||||
import {
|
||||
@ -101,9 +100,7 @@ export const NoteCard = ({
|
||||
<StyledCardContent>{body}</StyledCardContent>
|
||||
</StyledCardDetailsContainer>
|
||||
<StyledFooter>
|
||||
<ActivityTargetsInlineCell
|
||||
activity={note as unknown as GraphQLActivity}
|
||||
/>
|
||||
<ActivityTargetsInlineCell activity={note} />
|
||||
{note.comments && note.comments.length > 0 && (
|
||||
<StyledCommentIcon>
|
||||
<IconComment size={theme.icon.size.md} />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
|
||||
import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
@ -7,7 +7,9 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { ActivityTargetableObject } from '../../types/ActivityTargetableEntity';
|
||||
|
||||
export const useNotes = (targetableObject: ActivityTargetableObject) => {
|
||||
const { activityTargets } = useActivityTargets({ targetableObject });
|
||||
const { activityTargets } = useActivityTargetsForTargetableObject({
|
||||
targetableObject,
|
||||
});
|
||||
|
||||
const filter = {
|
||||
id: {
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
|
||||
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
|
||||
import { temporaryActivityForEditorState } from '@/activities/states/temporaryActivityForEditorState';
|
||||
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const ActivityActionBar = () => {
|
||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
||||
@ -15,9 +20,27 @@ export const ActivityActionBar = () => {
|
||||
refetchFindManyQuery: true,
|
||||
});
|
||||
|
||||
const [temporaryActivityForEditor, setTemporaryActivityForEditor] =
|
||||
useRecoilState(temporaryActivityForEditorState);
|
||||
|
||||
const { deleteActivityFromCache } = useDeleteActivityFromCache();
|
||||
|
||||
const [isCreatingActivity] = useRecoilState(isCreatingActivityState);
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const deleteActivity = () => {
|
||||
if (viewableActivityId) {
|
||||
deleteOneActivity?.(viewableActivityId);
|
||||
if (isCreatingActivity && isDefined(temporaryActivityForEditor)) {
|
||||
deleteActivityFromCache(temporaryActivityForEditor);
|
||||
setTemporaryActivityForEditor(null);
|
||||
} else {
|
||||
deleteOneActivity?.(viewableActivityId);
|
||||
// TODO: find a better way to do this with custom optimistic rendering for activities
|
||||
apolloClient.refetchQueries({
|
||||
include: ['FindManyActivities'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setIsRightDrawerOpen(false);
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { ActivityEditor } from '@/activities/components/ActivityEditor';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useActivityById } from '@/activities/hooks/useActivityById';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
@ -21,23 +16,16 @@ const StyledContainer = styled.div`
|
||||
type RightDrawerActivityProps = {
|
||||
activityId: string;
|
||||
showComment?: boolean;
|
||||
autoFillTitle?: boolean;
|
||||
fillTitleFromBody?: boolean;
|
||||
};
|
||||
|
||||
export const RightDrawerActivity = ({
|
||||
activityId,
|
||||
showComment = true,
|
||||
autoFillTitle = false,
|
||||
fillTitleFromBody = false,
|
||||
}: RightDrawerActivityProps) => {
|
||||
const setEntityFields = useSetRecoilState(recordStoreFamilyState(activityId));
|
||||
|
||||
const { record: activity } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
objectRecordId: activityId,
|
||||
skip: !activityId,
|
||||
onCompleted: (activity: Activity) => {
|
||||
setEntityFields(activity ?? {});
|
||||
},
|
||||
const { activity } = useActivityById({
|
||||
activityId,
|
||||
});
|
||||
|
||||
if (!activity) {
|
||||
@ -49,7 +37,7 @@ export const RightDrawerActivity = ({
|
||||
<ActivityEditor
|
||||
activity={activity}
|
||||
showComment={showComment}
|
||||
autoFillTitle={autoFillTitle}
|
||||
fillTitleFromBody={fillTitleFromBody}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -13,7 +13,7 @@ export const RightDrawerCreateActivity = () => {
|
||||
<RightDrawerActivity
|
||||
activityId={viewableActivityId}
|
||||
showComment={false}
|
||||
autoFillTitle={true}
|
||||
fillTitleFromBody={true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const activityTitleHasBeenSetFamilyState = atomFamily<
|
||||
boolean,
|
||||
{ activityId: string }
|
||||
>({
|
||||
key: 'activityTitleHasBeenSetFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const isCreatingActivityState = atom<boolean>({
|
||||
key: 'isCreatingActivityState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { ActivityForEditor } from '@/activities/types/ActivityForEditor';
|
||||
|
||||
export const temporaryActivityForEditorState = atom<ActivityForEditor | null>({
|
||||
key: 'temporaryActivityForEditorState',
|
||||
default: null,
|
||||
});
|
||||
@ -1,71 +0,0 @@
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import CommentCounter from '@/activities/comment/CommentCounter';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { UserChip } from '@/users/components/UserChip';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { beautifyExactDate } from '~/utils/date-utils';
|
||||
|
||||
type TimelineActivityCardFooterProps = {
|
||||
activity: Pick<Activity, 'id' | 'dueAt' | 'comments'> & {
|
||||
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
|
||||
};
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
const StyledVerticalSeparator = styled.div`
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
const StyledComment = styled.div`
|
||||
margin-left: auto;
|
||||
`;
|
||||
export const TimelineActivityCardFooter = ({
|
||||
activity,
|
||||
}: TimelineActivityCardFooterProps) => {
|
||||
const hasComments = isNonEmptyArray(activity.comments || []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(activity.assignee || activity.dueAt || hasComments) && (
|
||||
<StyledContainer>
|
||||
{activity.assignee && (
|
||||
<UserChip
|
||||
id={activity.assignee.id}
|
||||
name={
|
||||
activity.assignee.name.firstName +
|
||||
' ' +
|
||||
activity.assignee.name.lastName ?? ''
|
||||
}
|
||||
avatarUrl={activity.assignee.avatarUrl ?? ''}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activity.dueAt && (
|
||||
<>
|
||||
{activity.assignee && <StyledVerticalSeparator />}
|
||||
{beautifyExactDate(activity.dueAt)}
|
||||
</>
|
||||
)}
|
||||
<StyledComment>
|
||||
{hasComments && (
|
||||
<CommentCounter commentCount={activity.comments?.length || 0} />
|
||||
)}
|
||||
</StyledComment>
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { Button, ButtonGroup } from 'tsup.ui.index';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { useOpenCreateActivityDrawerV2 } from '@/activities/hooks/useOpenCreateActivityDrawerV2';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import {
|
||||
IconCheckbox,
|
||||
@ -19,7 +19,7 @@ export const TimelineCreateButtonGroup = ({
|
||||
const { getActiveTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
|
||||
const setActiveTabId = useSetRecoilState(getActiveTabIdState());
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||
const openCreateActivity = useOpenCreateActivityDrawerV2();
|
||||
|
||||
return (
|
||||
<ButtonGroup variant={'secondary'}>
|
||||
@ -30,6 +30,7 @@ export const TimelineCreateButtonGroup = ({
|
||||
openCreateActivity({
|
||||
type: 'Note',
|
||||
targetableObjects: [targetableObject],
|
||||
timelineTargetableObject: targetableObject,
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -40,6 +41,7 @@ export const TimelineCreateButtonGroup = ({
|
||||
openCreateActivity({
|
||||
type: 'Task',
|
||||
targetableObjects: [targetableObject],
|
||||
timelineTargetableObject: targetableObject,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1,89 +0,0 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const useInjectIntoTimelineActivitiesQuery = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const {
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
findManyRecordsQuery: findManyActivitiesQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const injectIntoTimelineActivitiesQuery = ({
|
||||
activityTargets,
|
||||
activityToInject,
|
||||
}: {
|
||||
activityTargets: ActivityTarget[];
|
||||
activityToInject: Activity;
|
||||
}) => {
|
||||
const activityIds = activityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const timelineActivitiesQueryVariables =
|
||||
makeTimelineActivitiesQueryVariables({
|
||||
activityIds,
|
||||
});
|
||||
|
||||
const exitistingActivitiesQueryResult = apolloClient.readQuery({
|
||||
query: findManyActivitiesQuery,
|
||||
variables: timelineActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
const extistingActivities = exitistingActivitiesQueryResult
|
||||
? getRecordsFromRecordConnection({
|
||||
recordConnection: exitistingActivitiesQueryResult[
|
||||
objectMetadataItemActivity.namePlural
|
||||
] as ObjectRecordConnection<Activity>,
|
||||
})
|
||||
: [];
|
||||
|
||||
const newActivity = {
|
||||
...activityToInject,
|
||||
__typename: 'Activity',
|
||||
};
|
||||
|
||||
const newActivitiesSortedAsActivitiesQuery = [
|
||||
newActivity,
|
||||
...extistingActivities,
|
||||
];
|
||||
|
||||
const newActivityIdsSortedAsActivityTargetsQuery = [
|
||||
...extistingActivities,
|
||||
newActivity,
|
||||
].map((activity) => activity.id);
|
||||
|
||||
const newTimelineActivitiesQueryVariables =
|
||||
makeTimelineActivitiesQueryVariables({
|
||||
activityIds: newActivityIdsSortedAsActivityTargetsQuery,
|
||||
});
|
||||
|
||||
const newActivityConnectionForCache = getRecordConnectionFromRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
records: newActivitiesSortedAsActivitiesQuery,
|
||||
});
|
||||
|
||||
apolloClient.writeQuery({
|
||||
query: findManyActivitiesQuery,
|
||||
variables: newTimelineActivitiesQueryVariables,
|
||||
data: {
|
||||
[objectMetadataItemActivity.namePlural]: newActivityConnectionForCache,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectIntoTimelineActivitiesNextQuery: injectIntoTimelineActivitiesQuery,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,124 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
|
||||
export const useInjectIntoTimelineActivitiesQueryAfterDrawerMount = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache: overwriteFindManyActivitiesInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItemOnly({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivityTargetsQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const {
|
||||
readFindManyRecordsQueryInCache: readFindManyActivitiesQueryInCache,
|
||||
} = useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const {
|
||||
upsertFindManyRecordsQueryInCache:
|
||||
overwriteFindManyActivityTargetsQueryInCache,
|
||||
} = useUpsertFindManyRecordsQueryInCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const injectIntoTimelineActivitiesQueryAfterDrawerMount = ({
|
||||
activityToInject,
|
||||
activityTargetsToInject,
|
||||
timelineTargetableObject,
|
||||
}: {
|
||||
activityToInject: Activity;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
timelineTargetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const newActivity = {
|
||||
...activityToInject,
|
||||
__typename: 'Activity',
|
||||
};
|
||||
|
||||
const targetObjectFieldName = getActivityTargetObjectFieldIdName({
|
||||
nameSingular: timelineTargetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const activitiyTargetsForTargetableObjectQueryVariables = {
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: timelineTargetableObject.id,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const existingActivityTargetsForTargetableObject =
|
||||
readFindManyActivityTargetsQueryInCache({
|
||||
queryVariables: activitiyTargetsForTargetableObjectQueryVariables,
|
||||
});
|
||||
|
||||
const newActivityTargetsForTargetableObject = [
|
||||
...existingActivityTargetsForTargetableObject,
|
||||
...activityTargetsToInject,
|
||||
];
|
||||
|
||||
const existingActivityIds = existingActivityTargetsForTargetableObject
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const timelineActivitiesQueryVariablesBeforeDrawerMount =
|
||||
makeTimelineActivitiesQueryVariables({
|
||||
activityIds: existingActivityIds,
|
||||
});
|
||||
|
||||
const existingActivities = readFindManyActivitiesQueryInCache({
|
||||
queryVariables: timelineActivitiesQueryVariablesBeforeDrawerMount,
|
||||
});
|
||||
|
||||
const activityIdsAfterDrawerMount = [
|
||||
...existingActivityIds,
|
||||
newActivity.id,
|
||||
];
|
||||
|
||||
const timelineActivitiesQueryVariablesAfterDrawerMount =
|
||||
makeTimelineActivitiesQueryVariables({
|
||||
activityIds: activityIdsAfterDrawerMount,
|
||||
});
|
||||
|
||||
overwriteFindManyActivityTargetsQueryInCache({
|
||||
objectRecordsToOverwrite: newActivityTargetsForTargetableObject,
|
||||
queryVariables: activitiyTargetsForTargetableObjectQueryVariables,
|
||||
});
|
||||
|
||||
const newActivities = [newActivity, ...existingActivities];
|
||||
|
||||
overwriteFindManyActivitiesInCache({
|
||||
objectRecordsToOverwrite: newActivities,
|
||||
queryVariables: timelineActivitiesQueryVariablesAfterDrawerMount,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectIntoTimelineActivitiesQueryAfterDrawerMount,
|
||||
};
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
|
||||
import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
|
||||
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
@ -17,14 +17,12 @@ export const useTimelineActivities = ({
|
||||
activityTargets,
|
||||
loadingActivityTargets,
|
||||
initialized: initializedActivityTargets,
|
||||
} = useActivityTargets({
|
||||
} = useActivityTargetsForTargetableObject({
|
||||
targetableObject,
|
||||
});
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const [activities, setActivities] = useState<Activity[]>([]);
|
||||
|
||||
const activityIds = activityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
@ -35,7 +33,7 @@ export const useTimelineActivities = ({
|
||||
},
|
||||
);
|
||||
|
||||
const { records: activitiesFromRequest, loading: loadingActivities } =
|
||||
const { records: activities, loading: loadingActivities } =
|
||||
useFindManyRecords<Activity>({
|
||||
skip: loadingActivityTargets || !isNonEmptyArray(activityTargets),
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
@ -48,12 +46,6 @@ export const useTimelineActivities = ({
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingActivities) {
|
||||
setActivities(activitiesFromRequest);
|
||||
}
|
||||
}, [activitiesFromRequest, loadingActivities]);
|
||||
|
||||
const noActivityTargets =
|
||||
initializedActivityTargets && !isNonEmptyArray(activityTargets);
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ export const makeTimelineActivitiesQueryVariables = ({
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'AscNullsFirst',
|
||||
createdAt: 'DescNullsFirst',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { Comment } from '@/activities/types/Comment';
|
||||
import { WorkspaceMember } from '~/generated-metadata/graphql';
|
||||
|
||||
export type ActivityForEditor = Pick<
|
||||
Activity,
|
||||
'id' | 'title' | 'body' | 'type' | 'completedAt' | 'dueAt' | 'updatedAt'
|
||||
> & {
|
||||
comments?: Comment[];
|
||||
} & {
|
||||
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
|
||||
} & {
|
||||
activityTargets?: Array<
|
||||
Pick<
|
||||
ActivityTarget,
|
||||
'id' | 'companyId' | 'personId' | 'createdAt' | 'updatedAt' | 'activity'
|
||||
>
|
||||
>;
|
||||
};
|
||||
@ -6,8 +6,8 @@ export type ActivityTarget = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
companyId: string | null;
|
||||
personId: string | null;
|
||||
companyId?: string | null;
|
||||
personId?: string | null;
|
||||
activity: Pick<Activity, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
person?: Pick<Person, 'id' | 'name' | 'avatarUrl'> | null;
|
||||
company?: Pick<Company, 'id' | 'name' | 'domainName'> | null;
|
||||
|
||||
@ -24,13 +24,17 @@ export const getActivityTargetsToCreateFromTargetableObjects = ({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
return {
|
||||
const activityTarget = {
|
||||
[targetableObject.targetObjectNameSingular]:
|
||||
targetableObject.targetObjectRecord,
|
||||
[targetableObjectFieldIdName]: targetableObject.id,
|
||||
activityId,
|
||||
id: v4(),
|
||||
};
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
} as Partial<ActivityTarget>;
|
||||
|
||||
return activityTarget;
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { Comment } from '@/activities/types/Comment';
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useActivityConnectionUtils = () => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const makeActivityWithoutConnection = (activityWithConnections: any) => {
|
||||
if (!isDefined(activityWithConnections)) {
|
||||
return { activity: null };
|
||||
}
|
||||
|
||||
const hasActivityTargetsConnection = isObjectRecordConnection(
|
||||
CoreObjectNameSingular.ActivityTarget,
|
||||
activityWithConnections?.activityTargets,
|
||||
);
|
||||
|
||||
const activityTargets: ActivityTarget[] = [];
|
||||
|
||||
if (hasActivityTargetsConnection) {
|
||||
const newActivityTargets = mapConnectionToRecords({
|
||||
objectRecordConnection: activityWithConnections?.activityTargets,
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
depth: 5,
|
||||
}) as ActivityTarget[];
|
||||
|
||||
activityTargets.push(...newActivityTargets);
|
||||
}
|
||||
|
||||
const hasCommentsConnection = isObjectRecordConnection(
|
||||
CoreObjectNameSingular.Comment,
|
||||
activityWithConnections?.comments,
|
||||
);
|
||||
|
||||
const comments: Comment[] = [];
|
||||
|
||||
if (hasCommentsConnection) {
|
||||
const newComments = mapConnectionToRecords({
|
||||
objectRecordConnection: activityWithConnections?.comments,
|
||||
objectNameSingular: CoreObjectNameSingular.Comment,
|
||||
depth: 5,
|
||||
}) as Comment[];
|
||||
|
||||
comments.push(...newComments);
|
||||
}
|
||||
|
||||
const activity: Activity = {
|
||||
...activityWithConnections,
|
||||
activityTargets,
|
||||
comments,
|
||||
};
|
||||
|
||||
return { activity };
|
||||
};
|
||||
|
||||
const makeActivityWithConnection = (activity: Activity) => {
|
||||
const activityTargetEdges = isNonEmptyArray(activity?.activityTargets)
|
||||
? activity.activityTargets.map((activityTarget) => ({
|
||||
node: activityTarget,
|
||||
cursor: '',
|
||||
}))
|
||||
: [];
|
||||
|
||||
const commentEdges = isNonEmptyArray(activity?.comments)
|
||||
? activity.comments.map((comment) => ({
|
||||
node: comment,
|
||||
cursor: '',
|
||||
}))
|
||||
: [];
|
||||
|
||||
const activityTargets = {
|
||||
edges: activityTargetEdges,
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<ActivityTarget>;
|
||||
|
||||
const comments = {
|
||||
edges: commentEdges,
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<Comment>;
|
||||
|
||||
const activityWithConnection = {
|
||||
...activity,
|
||||
activityTargets,
|
||||
comments,
|
||||
};
|
||||
|
||||
return { activityWithConnection };
|
||||
};
|
||||
|
||||
return {
|
||||
makeActivityWithoutConnection,
|
||||
makeActivityWithConnection,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user