Improved activity editor re-renders (#4149)
* Refactor task count * Fixed show page rerender * Less rerenders and way better title and body UX * Finished breaking down activity editor subscriptions * Removed console.log * Last console.log * Fixed bugs and cleaned
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { BlockNoteEditor } from '@blocknote/core';
|
import { BlockNoteEditor } from '@blocknote/core';
|
||||||
import { useBlockNote } from '@blocknote/react';
|
import { useBlockNote } from '@blocknote/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -9,6 +9,7 @@ import { useDebouncedCallback } from 'use-debounce';
|
|||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||||
|
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
|
||||||
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
|
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
|
||||||
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
@ -37,17 +38,27 @@ const StyledBlockNoteStyledContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type ActivityBodyEditorProps = {
|
type ActivityBodyEditorProps = {
|
||||||
activity: Activity;
|
activityId: string;
|
||||||
fillTitleFromBody: boolean;
|
fillTitleFromBody: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityBodyEditor = ({
|
export const ActivityBodyEditor = ({
|
||||||
activity,
|
activityId,
|
||||||
fillTitleFromBody,
|
fillTitleFromBody,
|
||||||
}: ActivityBodyEditorProps) => {
|
}: ActivityBodyEditorProps) => {
|
||||||
|
const [activityInStore] = useRecoilState(recordStoreFamilyState(activityId));
|
||||||
|
|
||||||
|
const activity = activityInStore as Activity | null;
|
||||||
|
|
||||||
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
|
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
|
||||||
activityTitleHasBeenSetFamilyState({
|
activityTitleHasBeenSetFamilyState({
|
||||||
activityId: activity.id,
|
activityId: activityId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [activityBody, setActivityBody] = useRecoilState(
|
||||||
|
activityBodyFamilyState({
|
||||||
|
activityId: activityId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -67,27 +78,31 @@ export const ActivityBodyEditor = ({
|
|||||||
const { upsertActivity } = useUpsertActivity();
|
const { upsertActivity } = useUpsertActivity();
|
||||||
|
|
||||||
const persistBodyDebounced = useDebouncedCallback((newBody: string) => {
|
const persistBodyDebounced = useDebouncedCallback((newBody: string) => {
|
||||||
upsertActivity({
|
if (activity) {
|
||||||
activity,
|
|
||||||
input: {
|
|
||||||
body: newBody,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const persistTitleAndBodyDebounced = useDebouncedCallback(
|
|
||||||
(newTitle: string, newBody: string) => {
|
|
||||||
upsertActivity({
|
upsertActivity({
|
||||||
activity,
|
activity,
|
||||||
input: {
|
input: {
|
||||||
title: newTitle,
|
|
||||||
body: newBody,
|
body: newBody,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
setActivityTitleHasBeenSet(true);
|
const persistTitleAndBodyDebounced = useDebouncedCallback(
|
||||||
|
(newTitle: string, newBody: string) => {
|
||||||
|
if (activity) {
|
||||||
|
upsertActivity({
|
||||||
|
activity,
|
||||||
|
input: {
|
||||||
|
title: newTitle,
|
||||||
|
body: newBody,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setActivityTitleHasBeenSet(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
500,
|
200,
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateTitleAndBody = useCallback(
|
const updateTitleAndBody = useCallback(
|
||||||
@ -104,28 +119,6 @@ export const ActivityBodyEditor = ({
|
|||||||
canCreateActivityState,
|
canCreateActivityState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBodyChange = useCallback(
|
|
||||||
(activityBody: string) => {
|
|
||||||
if (!canCreateActivity) {
|
|
||||||
setCanCreateActivity(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activityTitleHasBeenSet && fillTitleFromBody) {
|
|
||||||
updateTitleAndBody(activityBody);
|
|
||||||
} else {
|
|
||||||
persistBodyDebounced(activityBody);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
fillTitleFromBody,
|
|
||||||
persistBodyDebounced,
|
|
||||||
activityTitleHasBeenSet,
|
|
||||||
updateTitleAndBody,
|
|
||||||
setCanCreateActivity,
|
|
||||||
canCreateActivity,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const slashMenuItems = getSlashMenu();
|
const slashMenuItems = getSlashMenu();
|
||||||
|
|
||||||
const [uploadFile] = useUploadFileMutation();
|
const [uploadFile] = useUploadFileMutation();
|
||||||
@ -148,63 +141,105 @@ export const ActivityBodyEditor = ({
|
|||||||
return imageUrl;
|
return imageUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
const editor: BlockNoteEditor<typeof blockSpecs> | null = useBlockNote({
|
const handlePersistBody = useCallback(
|
||||||
initialContent:
|
(activityBody: string) => {
|
||||||
isNonEmptyString(activity.body) && activity.body !== '{}'
|
if (!canCreateActivity) {
|
||||||
? JSON.parse(activity.body)
|
setCanCreateActivity(true);
|
||||||
: undefined,
|
}
|
||||||
domAttributes: { editor: { class: 'editor' } },
|
|
||||||
onEditorContentChange: useRecoilCallback(
|
|
||||||
({ snapshot, set }) =>
|
|
||||||
(editor: BlockNoteEditor) => {
|
|
||||||
const newStringifiedBody =
|
|
||||||
JSON.stringify(editor.topLevelBlocks) ?? '';
|
|
||||||
|
|
||||||
set(recordStoreFamilyState(activity.id), (oldActivity) => {
|
if (!activityTitleHasBeenSet && fillTitleFromBody) {
|
||||||
|
updateTitleAndBody(activityBody);
|
||||||
|
} else {
|
||||||
|
persistBodyDebounced(activityBody);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
fillTitleFromBody,
|
||||||
|
persistBodyDebounced,
|
||||||
|
activityTitleHasBeenSet,
|
||||||
|
updateTitleAndBody,
|
||||||
|
setCanCreateActivity,
|
||||||
|
canCreateActivity,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBodyChange = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
(newStringifiedBody: string) => {
|
||||||
|
set(recordStoreFamilyState(activityId), (oldActivity) => {
|
||||||
|
return {
|
||||||
|
...oldActivity,
|
||||||
|
id: activityId,
|
||||||
|
body: newStringifiedBody,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
modifyActivityFromCache(activityId, {
|
||||||
|
body: () => {
|
||||||
|
return newStringifiedBody;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const activityTitleHasBeenSet = snapshot
|
||||||
|
.getLoadable(
|
||||||
|
activityTitleHasBeenSetFamilyState({
|
||||||
|
activityId: activityId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const blockBody = JSON.parse(newStringifiedBody);
|
||||||
|
const newTitleFromBody = blockBody[0]?.content?.[0]?.text as string;
|
||||||
|
|
||||||
|
if (!activityTitleHasBeenSet && fillTitleFromBody) {
|
||||||
|
set(recordStoreFamilyState(activityId), (oldActivity) => {
|
||||||
return {
|
return {
|
||||||
...oldActivity,
|
...oldActivity,
|
||||||
id: activity.id,
|
id: activityId,
|
||||||
body: newStringifiedBody,
|
title: newTitleFromBody,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
modifyActivityFromCache(activity.id, {
|
modifyActivityFromCache(activityId, {
|
||||||
body: () => {
|
title: () => {
|
||||||
return newStringifiedBody;
|
return newTitleFromBody;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const activityTitleHasBeenSet = snapshot
|
handlePersistBody(newStringifiedBody);
|
||||||
.getLoadable(
|
},
|
||||||
activityTitleHasBeenSetFamilyState({
|
[activityId, fillTitleFromBody, modifyActivityFromCache, handlePersistBody],
|
||||||
activityId: activity.id,
|
);
|
||||||
}),
|
|
||||||
)
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
const blockBody = JSON.parse(newStringifiedBody);
|
const handleBodyChangeDebounced = useDebouncedCallback(handleBodyChange, 500);
|
||||||
const newTitleFromBody = blockBody[0]?.content?.[0]?.text as string;
|
|
||||||
|
|
||||||
if (!activityTitleHasBeenSet && fillTitleFromBody) {
|
const handleEditorChange = (newEditor: BlockNoteEditor) => {
|
||||||
set(recordStoreFamilyState(activity.id), (oldActivity) => {
|
const newStringifiedBody = JSON.stringify(newEditor.topLevelBlocks) ?? '';
|
||||||
return {
|
|
||||||
...oldActivity,
|
|
||||||
id: activity.id,
|
|
||||||
title: newTitleFromBody,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
modifyActivityFromCache(activity.id, {
|
setActivityBody(newStringifiedBody);
|
||||||
title: () => {
|
|
||||||
return newTitleFromBody;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBodyChange(newStringifiedBody);
|
handleBodyChangeDebounced(newStringifiedBody);
|
||||||
},
|
};
|
||||||
[activity, fillTitleFromBody, modifyActivityFromCache, handleBodyChange],
|
|
||||||
),
|
const initialBody = useMemo(() => {
|
||||||
|
if (isNonEmptyString(activityBody) && activityBody !== '{}') {
|
||||||
|
return JSON.parse(activityBody);
|
||||||
|
} else if (
|
||||||
|
activity &&
|
||||||
|
isNonEmptyString(activity.body) &&
|
||||||
|
activity?.body !== '{}'
|
||||||
|
) {
|
||||||
|
return JSON.parse(activity.body);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}, [activity, activityBody]);
|
||||||
|
|
||||||
|
const editor: BlockNoteEditor<typeof blockSpecs> | null = useBlockNote({
|
||||||
|
initialContent: initialBody,
|
||||||
|
domAttributes: { editor: { class: 'editor' } },
|
||||||
|
onEditorContentChange: handleEditorChange,
|
||||||
slashMenuItems,
|
slashMenuItems,
|
||||||
blockSpecs: blockSpecs,
|
blockSpecs: blockSpecs,
|
||||||
uploadFile: handleUploadAttachment,
|
uploadFile: handleUploadAttachment,
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
|
||||||
|
export const ActivityBodyEffect = ({ activityId }: { activityId: string }) => {
|
||||||
|
const [activityFromStore] = useRecoilState(
|
||||||
|
recordStoreFamilyState(activityId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [activityBody, setActivityBody] = useRecoilState(
|
||||||
|
activityBodyFamilyState({ activityId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
activityBody === '' &&
|
||||||
|
activityFromStore &&
|
||||||
|
activityBody !== activityFromStore.body
|
||||||
|
) {
|
||||||
|
setActivityBody(activityFromStore.body);
|
||||||
|
}
|
||||||
|
}, [activityFromStore, activityBody, setActivityBody]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -4,7 +4,6 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { Comment } from '@/activities/comment/Comment';
|
import { Comment } from '@/activities/comment/Comment';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
|
||||||
import { Comment as CommentType } from '@/activities/types/Comment';
|
import { Comment as CommentType } from '@/activities/types/Comment';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
@ -50,12 +49,12 @@ const StyledThreadCommentTitle = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type ActivityCommentsProps = {
|
type ActivityCommentsProps = {
|
||||||
activity: Pick<Activity, 'id'>;
|
activityId: string;
|
||||||
scrollableContainerRef: React.RefObject<HTMLDivElement>;
|
scrollableContainerRef: React.RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityComments = ({
|
export const ActivityComments = ({
|
||||||
activity,
|
activityId,
|
||||||
scrollableContainerRef,
|
scrollableContainerRef,
|
||||||
}: ActivityCommentsProps) => {
|
}: ActivityCommentsProps) => {
|
||||||
const { createOneRecord: createOneComment } = useCreateOneRecord({
|
const { createOneRecord: createOneComment } = useCreateOneRecord({
|
||||||
@ -66,10 +65,10 @@ export const ActivityComments = ({
|
|||||||
|
|
||||||
const { records: comments } = useFindManyRecords({
|
const { records: comments } = useFindManyRecords({
|
||||||
objectNameSingular: CoreObjectNameSingular.Comment,
|
objectNameSingular: CoreObjectNameSingular.Comment,
|
||||||
skip: !isNonEmptyString(activity?.id),
|
skip: !isNonEmptyString(activityId),
|
||||||
filter: {
|
filter: {
|
||||||
activityId: {
|
activityId: {
|
||||||
eq: activity?.id ?? '',
|
eq: activityId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -87,7 +86,7 @@ export const ActivityComments = ({
|
|||||||
id: v4(),
|
id: v4(),
|
||||||
authorId: currentWorkspaceMember?.id ?? '',
|
authorId: currentWorkspaceMember?.id ?? '',
|
||||||
author: currentWorkspaceMember,
|
author: currentWorkspaceMember,
|
||||||
activityId: activity?.id ?? '',
|
activityId: activityId,
|
||||||
body: commentText,
|
body: commentText,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,28 +1,12 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
||||||
|
import { ActivityBodyEffect } from '@/activities/components/ActivityBodyEffect';
|
||||||
import { ActivityComments } from '@/activities/components/ActivityComments';
|
import { ActivityComments } from '@/activities/components/ActivityComments';
|
||||||
|
import { ActivityEditorFields } from '@/activities/components/ActivityEditorFields';
|
||||||
|
import { ActivityTitleEffect } from '@/activities/components/ActivityTitleEffect';
|
||||||
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
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 { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
|
||||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
|
||||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
|
||||||
import { Activity } from '@/activities/types/Activity';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
|
||||||
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 { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
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 { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
import { ActivityTitle } from './ActivityTitle';
|
import { ActivityTitle } from './ActivityTitle';
|
||||||
@ -60,152 +44,36 @@ const StyledTopContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type ActivityEditorProps = {
|
type ActivityEditorProps = {
|
||||||
activity: Activity;
|
activityId: string;
|
||||||
showComment?: boolean;
|
showComment?: boolean;
|
||||||
fillTitleFromBody?: boolean;
|
fillTitleFromBody?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityEditor = ({
|
export const ActivityEditor = ({
|
||||||
activity,
|
activityId,
|
||||||
showComment = true,
|
showComment = true,
|
||||||
fillTitleFromBody = false,
|
fillTitleFromBody = false,
|
||||||
}: ActivityEditorProps) => {
|
}: ActivityEditorProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener(
|
|
||||||
RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { upsertActivity } = useUpsertActivity();
|
|
||||||
const { deleteActivityFromCache } = useDeleteActivityFromCache();
|
|
||||||
|
|
||||||
const useUpsertOneActivityMutation: RecordUpdateHook = () => {
|
|
||||||
const upsertActivityMutation = async ({
|
|
||||||
variables,
|
|
||||||
}: RecordUpdateHookParams) => {
|
|
||||||
await upsertActivity({ activity, input: variables.updateOneRecordInput });
|
|
||||||
};
|
|
||||||
|
|
||||||
return [upsertActivityMutation, { loading: false }];
|
|
||||||
};
|
|
||||||
|
|
||||||
const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
|
||||||
objectRecordId: activity.id,
|
|
||||||
fieldMetadataName: 'dueAt',
|
|
||||||
fieldPosition: 0,
|
|
||||||
clearable: true,
|
|
||||||
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { FieldContextProvider: AssigneeFieldContextProvider } =
|
|
||||||
useFieldContext({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
|
||||||
objectRecordId: activity.id,
|
|
||||||
fieldMetadataName: 'assignee',
|
|
||||||
fieldPosition: 1,
|
|
||||||
clearable: true,
|
|
||||||
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isActivityInCreateMode, setIsActivityInCreateMode] = useRecoilState(
|
|
||||||
isActivityInCreateModeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isUpsertingActivityInDB] = useRecoilState(
|
|
||||||
isUpsertingActivityInDBState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [canCreateActivity] = useRecoilState(canCreateActivityState);
|
|
||||||
|
|
||||||
const [activityFromStore] = useRecoilState(
|
|
||||||
recordStoreFamilyState(activity.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { FieldContextProvider: ActivityTargetsContextProvider } =
|
|
||||||
useFieldContext({
|
|
||||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
|
||||||
objectRecordId: activity?.id ?? '',
|
|
||||||
fieldMetadataName: 'activityTargets',
|
|
||||||
fieldPosition: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
useRegisterClickOutsideListenerCallback({
|
|
||||||
callbackId: 'activity-editor',
|
|
||||||
callbackFunction: () => {
|
|
||||||
if (isUpsertingActivityInDB || !activityFromStore) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isActivityInCreateMode) {
|
|
||||||
if (canCreateActivity) {
|
|
||||||
upsertActivity({
|
|
||||||
activity,
|
|
||||||
input: {
|
|
||||||
title: activityFromStore.title,
|
|
||||||
body: activityFromStore.body,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
deleteActivityFromCache(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsActivityInCreateMode(false);
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
activityFromStore.title !== activity.title ||
|
|
||||||
activityFromStore.body !== activity.body
|
|
||||||
) {
|
|
||||||
upsertActivity({
|
|
||||||
activity,
|
|
||||||
input: {
|
|
||||||
title: activityFromStore.title,
|
|
||||||
body: activityFromStore.body,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!activity) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer ref={containerRef}>
|
<StyledContainer ref={containerRef}>
|
||||||
<StyledUpperPartContainer>
|
<StyledUpperPartContainer>
|
||||||
<StyledTopContainer>
|
<StyledTopContainer>
|
||||||
<ActivityTypeDropdown activity={activity} />
|
<ActivityTypeDropdown activityId={activityId} />
|
||||||
<ActivityTitle activity={activity} />
|
<ActivityTitleEffect activityId={activityId} />
|
||||||
<PropertyBox>
|
<ActivityTitle activityId={activityId} />
|
||||||
{activity.type === 'Task' &&
|
<ActivityEditorFields activityId={activityId} />
|
||||||
DueAtFieldContextProvider &&
|
|
||||||
AssigneeFieldContextProvider && (
|
|
||||||
<>
|
|
||||||
<DueAtFieldContextProvider>
|
|
||||||
<RecordInlineCell />
|
|
||||||
</DueAtFieldContextProvider>
|
|
||||||
<AssigneeFieldContextProvider>
|
|
||||||
<RecordInlineCell />
|
|
||||||
</AssigneeFieldContextProvider>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{ActivityTargetsContextProvider && (
|
|
||||||
<ActivityTargetsContextProvider>
|
|
||||||
<ActivityTargetsInlineCell activity={activity} />
|
|
||||||
</ActivityTargetsContextProvider>
|
|
||||||
)}
|
|
||||||
</PropertyBox>
|
|
||||||
</StyledTopContainer>
|
</StyledTopContainer>
|
||||||
</StyledUpperPartContainer>
|
</StyledUpperPartContainer>
|
||||||
|
<ActivityBodyEffect activityId={activityId} />
|
||||||
<ActivityBodyEditor
|
<ActivityBodyEditor
|
||||||
activity={activity}
|
activityId={activityId}
|
||||||
fillTitleFromBody={fillTitleFromBody}
|
fillTitleFromBody={fillTitleFromBody}
|
||||||
/>
|
/>
|
||||||
{showComment && (
|
{showComment && (
|
||||||
<ActivityComments
|
<ActivityComments
|
||||||
activity={activity}
|
activityId={activityId}
|
||||||
scrollableContainerRef={containerRef}
|
scrollableContainerRef={containerRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -0,0 +1,98 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
|
||||||
|
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||||
|
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
|
||||||
|
import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState';
|
||||||
|
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
||||||
|
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||||
|
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||||
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
|
|
||||||
|
export const ActivityEditorEffect = ({
|
||||||
|
activityId,
|
||||||
|
}: {
|
||||||
|
activityId: string;
|
||||||
|
}) => {
|
||||||
|
const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener(
|
||||||
|
RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { upsertActivity } = useUpsertActivity();
|
||||||
|
const { deleteActivityFromCache } = useDeleteActivityFromCache();
|
||||||
|
|
||||||
|
const upsertActivityCallback = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
() => {
|
||||||
|
const isUpsertingActivityInDB = snapshot
|
||||||
|
.getLoadable(isUpsertingActivityInDBState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const canCreateActivity = snapshot
|
||||||
|
.getLoadable(canCreateActivityState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const isActivityInCreateMode = snapshot
|
||||||
|
.getLoadable(isActivityInCreateModeState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const activityFromStore = snapshot
|
||||||
|
.getLoadable(recordStoreFamilyState(activityId))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const activity = activityFromStore as Activity | null;
|
||||||
|
|
||||||
|
const activityTitle = snapshot
|
||||||
|
.getLoadable(activityTitleFamilyState({ activityId }))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const activityBody = snapshot
|
||||||
|
.getLoadable(activityBodyFamilyState({ activityId }))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (isUpsertingActivityInDB || !activityFromStore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActivityInCreateMode && activity) {
|
||||||
|
if (canCreateActivity) {
|
||||||
|
upsertActivity({
|
||||||
|
activity,
|
||||||
|
input: {
|
||||||
|
title: activityFromStore.title,
|
||||||
|
body: activityFromStore.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
deleteActivityFromCache(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(isActivityInCreateModeState, false);
|
||||||
|
} else if (activity) {
|
||||||
|
if (
|
||||||
|
activity.title !== activityTitle ||
|
||||||
|
activity.body !== activityBody
|
||||||
|
) {
|
||||||
|
upsertActivity({
|
||||||
|
activity,
|
||||||
|
input: {
|
||||||
|
title: activityTitle,
|
||||||
|
body: activityBody,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activityId, deleteActivityFromCache, upsertActivity],
|
||||||
|
);
|
||||||
|
|
||||||
|
useRegisterClickOutsideListenerCallback({
|
||||||
|
callbackId: 'activity-editor',
|
||||||
|
callbackFunction: upsertActivityCallback,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||||
|
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||||
|
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 { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
|
||||||
|
export const ActivityEditorFields = ({
|
||||||
|
activityId,
|
||||||
|
}: {
|
||||||
|
activityId: string;
|
||||||
|
}) => {
|
||||||
|
const { upsertActivity } = useUpsertActivity();
|
||||||
|
|
||||||
|
const [activityFromStore] = useRecoilState(
|
||||||
|
recordStoreFamilyState(activityId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const activity = activityFromStore as Activity;
|
||||||
|
|
||||||
|
const useUpsertOneActivityMutation: RecordUpdateHook = () => {
|
||||||
|
const upsertActivityMutation = async ({
|
||||||
|
variables,
|
||||||
|
}: RecordUpdateHookParams) => {
|
||||||
|
if (activityFromStore) {
|
||||||
|
await upsertActivity({
|
||||||
|
activity: activityFromStore as Activity,
|
||||||
|
input: variables.updateOneRecordInput,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [upsertActivityMutation, { loading: false }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||||
|
objectRecordId: activityId,
|
||||||
|
fieldMetadataName: 'dueAt',
|
||||||
|
fieldPosition: 0,
|
||||||
|
clearable: true,
|
||||||
|
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { FieldContextProvider: AssigneeFieldContextProvider } =
|
||||||
|
useFieldContext({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||||
|
objectRecordId: activityId,
|
||||||
|
fieldMetadataName: 'assignee',
|
||||||
|
fieldPosition: 1,
|
||||||
|
clearable: true,
|
||||||
|
customUseUpdateOneObjectHook: useUpsertOneActivityMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { FieldContextProvider: ActivityTargetsContextProvider } =
|
||||||
|
useFieldContext({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||||
|
objectRecordId: activityId,
|
||||||
|
fieldMetadataName: 'activityTargets',
|
||||||
|
fieldPosition: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PropertyBox>
|
||||||
|
{activity.type === 'Task' &&
|
||||||
|
DueAtFieldContextProvider &&
|
||||||
|
AssigneeFieldContextProvider && (
|
||||||
|
<>
|
||||||
|
<DueAtFieldContextProvider>
|
||||||
|
<RecordInlineCell />
|
||||||
|
</DueAtFieldContextProvider>
|
||||||
|
<AssigneeFieldContextProvider>
|
||||||
|
<RecordInlineCell />
|
||||||
|
</AssigneeFieldContextProvider>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{ActivityTargetsContextProvider && (
|
||||||
|
<ActivityTargetsContextProvider>
|
||||||
|
<ActivityTargetsInlineCell activity={activity} />
|
||||||
|
</ActivityTargetsContextProvider>
|
||||||
|
)}
|
||||||
|
</PropertyBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum';
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||||
|
import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState';
|
||||||
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
|
import { activityTitleHasBeenSetFamilyState } from '@/activities/states/activityTitleHasBeenSetFamilyState';
|
||||||
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
@ -55,14 +56,20 @@ const StyledContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type ActivityTitleProps = {
|
type ActivityTitleProps = {
|
||||||
activity: Activity;
|
activityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityTitle = ({ activity }: ActivityTitleProps) => {
|
export const ActivityTitle = ({ activityId }: ActivityTitleProps) => {
|
||||||
const [activityInStore, setActivityInStore] = useRecoilState(
|
const [activityInStore, setActivityInStore] = useRecoilState(
|
||||||
recordStoreFamilyState(activity.id),
|
recordStoreFamilyState(activityId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [activityTitle, setActivityTitle] = useRecoilState(
|
||||||
|
activityTitleFamilyState({ activityId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const activity = activityInStore as Activity;
|
||||||
|
|
||||||
const [canCreateActivity, setCanCreateActivity] = useRecoilState(
|
const [canCreateActivity, setCanCreateActivity] = useRecoilState(
|
||||||
canCreateActivityState,
|
canCreateActivityState,
|
||||||
);
|
);
|
||||||
@ -96,7 +103,7 @@ export const ActivityTitle = ({ activity }: ActivityTitleProps) => {
|
|||||||
|
|
||||||
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
|
const [activityTitleHasBeenSet, setActivityTitleHasBeenSet] = useRecoilState(
|
||||||
activityTitleHasBeenSetFamilyState({
|
activityTitleHasBeenSetFamilyState({
|
||||||
activityId: activity.id,
|
activityId: activityId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -122,7 +129,7 @@ export const ActivityTitle = ({ activity }: ActivityTitleProps) => {
|
|||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const handleTitleChange = (newTitle: string) => {
|
const setTitleDebounced = useDebouncedCallback((newTitle: string) => {
|
||||||
setActivityInStore((currentActivity) => {
|
setActivityInStore((currentActivity) => {
|
||||||
return {
|
return {
|
||||||
...currentActivity,
|
...currentActivity,
|
||||||
@ -140,6 +147,12 @@ export const ActivityTitle = ({ activity }: ActivityTitleProps) => {
|
|||||||
return newTitle;
|
return newTitle;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
const handleTitleChange = (newTitle: string) => {
|
||||||
|
setActivityTitle(newTitle);
|
||||||
|
|
||||||
|
setTitleDebounced(newTitle);
|
||||||
|
|
||||||
persistTitleDebounced(newTitle);
|
persistTitleDebounced(newTitle);
|
||||||
};
|
};
|
||||||
@ -171,7 +184,7 @@ export const ActivityTitle = ({ activity }: ActivityTitleProps) => {
|
|||||||
ref={titleInputRef}
|
ref={titleInputRef}
|
||||||
placeholder={`${activity.type} title`}
|
placeholder={`${activity.type} title`}
|
||||||
onChange={(event) => handleTitleChange(event.target.value)}
|
onChange={(event) => handleTitleChange(event.target.value)}
|
||||||
value={activityInStore?.title ?? ''}
|
value={activityTitle}
|
||||||
completed={completed}
|
completed={completed}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { activityTitleFamilyState } from '@/activities/states/activityTitleFamilyState';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
|
||||||
|
export const ActivityTitleEffect = ({ activityId }: { activityId: string }) => {
|
||||||
|
const [activityFromStore] = useRecoilState(
|
||||||
|
recordStoreFamilyState(activityId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [activityTitle, setActivityTitle] = useRecoilState(
|
||||||
|
activityTitleFamilyState({ activityId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
activityTitle === '' &&
|
||||||
|
activityFromStore &&
|
||||||
|
activityTitle !== activityFromStore.title
|
||||||
|
) {
|
||||||
|
setActivityTitle(activityFromStore.title);
|
||||||
|
}
|
||||||
|
}, [activityFromStore, activityTitle, setActivityTitle]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import {
|
import {
|
||||||
Chip,
|
Chip,
|
||||||
ChipAccent,
|
ChipAccent,
|
||||||
@ -10,18 +11,21 @@ import {
|
|||||||
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
||||||
|
|
||||||
type ActivityTypeDropdownProps = {
|
type ActivityTypeDropdownProps = {
|
||||||
activity: Pick<Activity, 'type'>;
|
activityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityTypeDropdown = ({
|
export const ActivityTypeDropdown = ({
|
||||||
activity,
|
activityId,
|
||||||
}: ActivityTypeDropdownProps) => {
|
}: ActivityTypeDropdownProps) => {
|
||||||
|
const [activityInStore] = useRecoilState(recordStoreFamilyState(activityId));
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
label={activity.type}
|
label={activityInStore?.type}
|
||||||
leftComponent={
|
leftComponent={
|
||||||
activity.type === 'Note' ? (
|
activityInStore?.type === 'Note' ? (
|
||||||
<IconNotes size={theme.icon.size.md} />
|
<IconNotes size={theme.icon.size.md} />
|
||||||
) : (
|
) : (
|
||||||
<IconCheckbox size={theme.icon.size.md} />
|
<IconCheckbox size={theme.icon.size.md} />
|
||||||
|
|||||||
@ -90,7 +90,7 @@ export const useActivities = ({
|
|||||||
const loading = loadingActivities || loadingActivityTargets;
|
const loading = loadingActivities || loadingActivityTargets;
|
||||||
|
|
||||||
// TODO: fix connection in relation => automatically change to an array
|
// TODO: fix connection in relation => automatically change to an array
|
||||||
const activities = activitiesWithConnection
|
const activities: Activity[] = activitiesWithConnection
|
||||||
?.map(makeActivityWithoutConnection as any)
|
?.map(makeActivityWithoutConnection as any)
|
||||||
.map(({ activity }: any) => activity);
|
.map(({ activity }: any) => activity);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,10 @@ export const useActivityTargetsForTargetableObjects = ({
|
|||||||
targetableObjects,
|
targetableObjects,
|
||||||
skip,
|
skip,
|
||||||
}: {
|
}: {
|
||||||
targetableObjects: ActivityTargetableObject[];
|
targetableObjects: Pick<
|
||||||
|
ActivityTargetableObject,
|
||||||
|
'id' | 'targetObjectNameSingular'
|
||||||
|
>[];
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const activityTargetsFilter = getActivityTargetsFilter({
|
const activityTargetsFilter = getActivityTargetsFilter({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useAttachRelationInBothDirections } from '@/activities/hooks/useAttachRelationInBothDirections';
|
import { useAttachRelationInBothDirections } from '@/activities/hooks/useAttachRelationInBothDirections';
|
||||||
@ -6,13 +6,15 @@ import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-
|
|||||||
import { Activity, ActivityType } from '@/activities/types/Activity';
|
import { Activity, ActivityType } from '@/activities/types/Activity';
|
||||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { getActivityTargetsToCreateFromTargetableObjects } from '@/activities/utils/getActivityTargetsToCreateFromTargetableObjects';
|
import { makeActivityTargetsToCreateFromTargetableObjects } from '@/activities/utils/getActivityTargetsToCreateFromTargetableObjects';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useCreateManyRecordsInCache } from '@/object-record/hooks/useCreateManyRecordsInCache';
|
import { useCreateManyRecordsInCache } from '@/object-record/hooks/useCreateManyRecordsInCache';
|
||||||
import { useCreateOneRecordInCache } from '@/object-record/hooks/useCreateOneRecordInCache';
|
import { useCreateOneRecordInCache } from '@/object-record/hooks/useCreateOneRecordInCache';
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const useCreateActivityInCache = () => {
|
export const useCreateActivityInCache = () => {
|
||||||
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
|
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
|
||||||
@ -39,58 +41,86 @@ export const useCreateActivityInCache = () => {
|
|||||||
const { attachRelationInBothDirections } =
|
const { attachRelationInBothDirections } =
|
||||||
useAttachRelationInBothDirections();
|
useAttachRelationInBothDirections();
|
||||||
|
|
||||||
const createActivityInCache = ({
|
const createActivityInCache = useRecoilCallback(
|
||||||
type,
|
({ snapshot, set }) =>
|
||||||
targetableObjects,
|
({
|
||||||
customAssignee,
|
type,
|
||||||
}: {
|
|
||||||
type: ActivityType;
|
|
||||||
targetableObjects: ActivityTargetableObject[];
|
|
||||||
customAssignee?: WorkspaceMember;
|
|
||||||
}) => {
|
|
||||||
const activityId = v4();
|
|
||||||
|
|
||||||
const createdActivityInCache = createOneActivityInCache({
|
|
||||||
id: activityId,
|
|
||||||
author: currentWorkspaceMemberRecord,
|
|
||||||
authorId: currentWorkspaceMemberRecord?.id,
|
|
||||||
assignee: customAssignee ?? currentWorkspaceMemberRecord,
|
|
||||||
assigneeId: customAssignee?.id ?? currentWorkspaceMemberRecord?.id,
|
|
||||||
type,
|
|
||||||
});
|
|
||||||
|
|
||||||
const activityTargetsToCreate =
|
|
||||||
getActivityTargetsToCreateFromTargetableObjects({
|
|
||||||
activityId,
|
|
||||||
targetableObjects,
|
targetableObjects,
|
||||||
});
|
customAssignee,
|
||||||
|
}: {
|
||||||
|
type: ActivityType;
|
||||||
|
targetableObjects: ActivityTargetableObject[];
|
||||||
|
customAssignee?: WorkspaceMember;
|
||||||
|
}) => {
|
||||||
|
const activityId = v4();
|
||||||
|
|
||||||
const createdActivityTargetsInCache = createManyActivityTargetsInCache(
|
const createdActivityInCache = createOneActivityInCache({
|
||||||
activityTargetsToCreate,
|
id: activityId,
|
||||||
);
|
author: currentWorkspaceMemberRecord,
|
||||||
|
authorId: currentWorkspaceMemberRecord?.id,
|
||||||
|
assignee: customAssignee ?? currentWorkspaceMemberRecord,
|
||||||
|
assigneeId: customAssignee?.id ?? currentWorkspaceMemberRecord?.id,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
injectIntoActivityTargetInlineCellCache({
|
const targetObjectRecords = targetableObjects
|
||||||
activityId,
|
.map((targetableObject) => {
|
||||||
activityTargetsToInject: createdActivityTargetsInCache,
|
const targetObject = snapshot
|
||||||
});
|
.getLoadable(recordStoreFamilyState(targetableObject.id))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
attachRelationInBothDirections({
|
return targetObject;
|
||||||
sourceRecord: createdActivityInCache,
|
})
|
||||||
fieldNameOnSourceRecord: 'activityTargets',
|
.filter(isDefined);
|
||||||
sourceObjectNameSingular: CoreObjectNameSingular.Activity,
|
|
||||||
fieldNameOnTargetRecord: 'activity',
|
|
||||||
targetObjectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
|
||||||
targetRecords: createdActivityTargetsInCache,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
const activityTargetsToCreate =
|
||||||
createdActivityInCache: {
|
makeActivityTargetsToCreateFromTargetableObjects({
|
||||||
...createdActivityInCache,
|
activityId,
|
||||||
activityTargets: createdActivityTargetsInCache,
|
targetableObjects,
|
||||||
|
targetObjectRecords,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdActivityTargetsInCache = createManyActivityTargetsInCache(
|
||||||
|
activityTargetsToCreate,
|
||||||
|
);
|
||||||
|
|
||||||
|
injectIntoActivityTargetInlineCellCache({
|
||||||
|
activityId,
|
||||||
|
activityTargetsToInject: createdActivityTargetsInCache,
|
||||||
|
});
|
||||||
|
|
||||||
|
attachRelationInBothDirections({
|
||||||
|
sourceRecord: createdActivityInCache,
|
||||||
|
fieldNameOnSourceRecord: 'activityTargets',
|
||||||
|
sourceObjectNameSingular: CoreObjectNameSingular.Activity,
|
||||||
|
fieldNameOnTargetRecord: 'activity',
|
||||||
|
targetObjectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||||
|
targetRecords: createdActivityTargetsInCache,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: should refactor when refactoring make activity connection utils
|
||||||
|
set(recordStoreFamilyState(activityId), {
|
||||||
|
...createdActivityInCache,
|
||||||
|
activityTargets: createdActivityTargetsInCache,
|
||||||
|
comments: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
createdActivityInCache: {
|
||||||
|
...createdActivityInCache,
|
||||||
|
activityTargets: createdActivityTargetsInCache,
|
||||||
|
},
|
||||||
|
createdActivityTargetsInCache,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
createdActivityTargetsInCache,
|
[
|
||||||
};
|
attachRelationInBothDirections,
|
||||||
};
|
createManyActivityTargetsInCache,
|
||||||
|
createOneActivityInCache,
|
||||||
|
currentWorkspaceMemberRecord,
|
||||||
|
injectIntoActivityTargetInlineCellCache,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createActivityInCache,
|
createActivityInCache,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
|
||||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
@ -15,21 +14,21 @@ export const useOpenActivityRightDrawer = () => {
|
|||||||
const [viewableActivityId, setViewableActivityId] = useRecoilState(
|
const [viewableActivityId, setViewableActivityId] = useRecoilState(
|
||||||
viewableActivityIdState,
|
viewableActivityIdState,
|
||||||
);
|
);
|
||||||
const [, setActivityInDrawer] = useRecoilState(activityInDrawerState);
|
const [, setActivityIdInDrawer] = useRecoilState(activityIdInDrawerState);
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
return (activity: Activity) => {
|
return (activityId: string) => {
|
||||||
if (
|
if (
|
||||||
isRightDrawerOpen &&
|
isRightDrawerOpen &&
|
||||||
rightDrawerPage === RightDrawerPages.EditActivity &&
|
rightDrawerPage === RightDrawerPages.EditActivity &&
|
||||||
viewableActivityId === activity.id
|
viewableActivityId === activityId
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
setViewableActivityId(activity.id);
|
setViewableActivityId(activityId);
|
||||||
setActivityInDrawer(activity);
|
setActivityIdInDrawer(activityId);
|
||||||
openRightDrawer(RightDrawerPages.EditActivity);
|
openRightDrawer(RightDrawerPages.EditActivity);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { useCreateActivityInCache } from '@/activities/hooks/useCreateActivityInCache';
|
import { useCreateActivityInCache } from '@/activities/hooks/useCreateActivityInCache';
|
||||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
||||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||||
@ -34,7 +34,7 @@ export const useOpenCreateActivityDrawer = () => {
|
|||||||
temporaryActivityForEditorState,
|
temporaryActivityForEditorState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const setActivityInDrawer = useSetRecoilState(activityInDrawerState);
|
const setActivityIdInDrawer = useSetRecoilState(activityIdInDrawerState);
|
||||||
|
|
||||||
const [, setIsUpsertingActivityInDB] = useRecoilState(
|
const [, setIsUpsertingActivityInDB] = useRecoilState(
|
||||||
isUpsertingActivityInDBState,
|
isUpsertingActivityInDBState,
|
||||||
@ -55,7 +55,7 @@ export const useOpenCreateActivityDrawer = () => {
|
|||||||
customAssignee,
|
customAssignee,
|
||||||
});
|
});
|
||||||
|
|
||||||
setActivityInDrawer(createdActivityInCache);
|
setActivityIdInDrawer(createdActivityInCache.id);
|
||||||
setTemporaryActivityForEditor(createdActivityInCache);
|
setTemporaryActivityForEditor(createdActivityInCache);
|
||||||
setIsCreatingActivity(true);
|
setIsCreatingActivity(true);
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
|
|||||||
@ -6,13 +6,13 @@ import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB'
|
|||||||
import { useInjectIntoActivitiesQueries } from '@/activities/hooks/useInjectIntoActivitiesQueries';
|
import { useInjectIntoActivitiesQueries } from '@/activities/hooks/useInjectIntoActivitiesQueries';
|
||||||
import { useInjectIntoActivityTargetsQueries } from '@/activities/hooks/useInjectIntoActivityTargetsQueries';
|
import { useInjectIntoActivityTargetsQueries } from '@/activities/hooks/useInjectIntoActivityTargetsQueries';
|
||||||
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
|
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
|
||||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||||
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
|
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
|
||||||
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
|
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
|
||||||
import { useInjectIntoTimelineActivitiesQueries } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQueries';
|
import { useInjectIntoTimelineActivitiesQueries } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQueries';
|
||||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
|
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
@ -34,7 +34,7 @@ export const useUpsertActivity = () => {
|
|||||||
isUpsertingActivityInDBState,
|
isUpsertingActivityInDBState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const setActivityInDrawer = useSetRecoilState(activityInDrawerState);
|
const setActivityIdInDrawer = useSetRecoilState(activityIdInDrawerState);
|
||||||
|
|
||||||
const objectShowPageTargetableObject = useRecoilValue(
|
const objectShowPageTargetableObject = useRecoilValue(
|
||||||
objectShowPageTargetableObjectState,
|
objectShowPageTargetableObjectState,
|
||||||
@ -169,7 +169,7 @@ export const useUpsertActivity = () => {
|
|||||||
|
|
||||||
await createActivityInDB(activityToCreate);
|
await createActivityInDB(activityToCreate);
|
||||||
|
|
||||||
setActivityInDrawer(activityToCreate);
|
setActivityIdInDrawer(activityToCreate.id);
|
||||||
|
|
||||||
setIsActivityInCreateMode(false);
|
setIsActivityInCreateMode(false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -94,7 +94,7 @@ export const NoteCard = ({
|
|||||||
<FieldContext.Provider value={fieldContext as GenericFieldContextType}>
|
<FieldContext.Provider value={fieldContext as GenericFieldContextType}>
|
||||||
<StyledCard isSingleNote={isSingleNote}>
|
<StyledCard isSingleNote={isSingleNote}>
|
||||||
<StyledCardDetailsContainer
|
<StyledCardDetailsContainer
|
||||||
onClick={() => openActivityRightDrawer(note)}
|
onClick={() => openActivityRightDrawer(note.id)}
|
||||||
>
|
>
|
||||||
<StyledNoteTitle>{note.title ?? 'Task Title'}</StyledNoteTitle>
|
<StyledNoteTitle>{note.title ?? 'Task Title'}</StyledNoteTitle>
|
||||||
<StyledCardContent>{body}</StyledCardContent>
|
<StyledCardContent>{body}</StyledCardContent>
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyArray } from '@sniptt/guards';
|
import { isNonEmptyArray } from '@sniptt/guards';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
|
import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { useRemoveFromActivitiesQueries } from '@/activities/hooks/useRemoveFromActivitiesQueries';
|
import { useRemoveFromActivitiesQueries } from '@/activities/hooks/useRemoveFromActivitiesQueries';
|
||||||
import { useRemoveFromActivityTargetsQueries } from '@/activities/hooks/useRemoveFromActivityTargetsQueries';
|
import { useRemoveFromActivityTargetsQueries } from '@/activities/hooks/useRemoveFromActivityTargetsQueries';
|
||||||
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
|
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
|
||||||
import { activityInDrawerState } from '@/activities/states/activityInDrawerState';
|
import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
|
||||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
||||||
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
|
||||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||||
@ -17,11 +17,13 @@ import { viewableActivityIdState } from '@/activities/states/viewableActivityIdS
|
|||||||
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
|
import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
|
||||||
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
|
import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
|
||||||
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
|
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
|
||||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
|
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { getChildRelationArray } from '@/object-record/utils/getChildRelationArray';
|
||||||
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
|
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
|
||||||
import { IconPlus, IconTrash } from '@/ui/display/icon';
|
import { IconPlus, IconTrash } from '@/ui/display/icon';
|
||||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||||
@ -35,7 +37,7 @@ const StyledButtonContainer = styled.div`
|
|||||||
|
|
||||||
export const ActivityActionBar = () => {
|
export const ActivityActionBar = () => {
|
||||||
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
const viewableActivityId = useRecoilValue(viewableActivityIdState);
|
||||||
const activityInDrawer = useRecoilValue(activityInDrawerState);
|
const activityIdInDrawer = useRecoilValue(activityIdInDrawerState);
|
||||||
|
|
||||||
const activityTargetableEntityArray = useRecoilValue(
|
const activityTargetableEntityArray = useRecoilValue(
|
||||||
activityTargetableEntityArrayState,
|
activityTargetableEntityArrayState,
|
||||||
@ -60,9 +62,11 @@ export const ActivityActionBar = () => {
|
|||||||
const [isUpsertingActivityInDB] = useRecoilState(
|
const [isUpsertingActivityInDB] = useRecoilState(
|
||||||
isUpsertingActivityInDBState,
|
isUpsertingActivityInDBState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectShowPageTargetableObject = useRecoilValue(
|
const objectShowPageTargetableObject = useRecoilValue(
|
||||||
objectShowPageTargetableObjectState,
|
objectShowPageTargetableObjectState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||||
|
|
||||||
const currentCompletedTaskQueryVariables = useRecoilValue(
|
const currentCompletedTaskQueryVariables = useRecoilValue(
|
||||||
@ -85,90 +89,130 @@ export const ActivityActionBar = () => {
|
|||||||
const weAreOnObjectShowPage = pathname.startsWith('/object');
|
const weAreOnObjectShowPage = pathname.startsWith('/object');
|
||||||
const weAreOnTaskPage = pathname.startsWith('/tasks');
|
const weAreOnTaskPage = pathname.startsWith('/tasks');
|
||||||
|
|
||||||
const deleteActivity = async () => {
|
const deleteActivity = useRecoilCallback(
|
||||||
setIsRightDrawerOpen(false);
|
({ snapshot }) =>
|
||||||
|
async () => {
|
||||||
if (viewableActivityId) {
|
if (!activityIdInDrawer) {
|
||||||
if (isActivityInCreateMode && isDefined(temporaryActivityForEditor)) {
|
throw new Error(
|
||||||
deleteActivityFromCache(temporaryActivityForEditor);
|
'activityIdInDrawer is not defined, this should not happen',
|
||||||
setTemporaryActivityForEditor(null);
|
);
|
||||||
} else {
|
|
||||||
if (activityInDrawer) {
|
|
||||||
const activityTargetIdsToDelete =
|
|
||||||
activityInDrawer?.activityTargets.map(mapToRecordId) ?? [];
|
|
||||||
|
|
||||||
if (weAreOnTaskPage) {
|
|
||||||
removeFromActivitiesQueries({
|
|
||||||
activityIdToRemove: viewableActivityId,
|
|
||||||
targetableObjects: [],
|
|
||||||
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
|
|
||||||
activitiesOrderByVariables:
|
|
||||||
currentCompletedTaskQueryVariables?.orderBy,
|
|
||||||
});
|
|
||||||
|
|
||||||
removeFromActivitiesQueries({
|
|
||||||
activityIdToRemove: viewableActivityId,
|
|
||||||
targetableObjects: [],
|
|
||||||
activitiesFilters: currentIncompleteTaskQueryVariables?.filter,
|
|
||||||
activitiesOrderByVariables:
|
|
||||||
currentIncompleteTaskQueryVariables?.orderBy,
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
weAreOnObjectShowPage &&
|
|
||||||
isDefined(objectShowPageTargetableObject)
|
|
||||||
) {
|
|
||||||
removeFromActivitiesQueries({
|
|
||||||
activityIdToRemove: viewableActivityId,
|
|
||||||
targetableObjects: [objectShowPageTargetableObject],
|
|
||||||
activitiesFilters: {},
|
|
||||||
activitiesOrderByVariables:
|
|
||||||
FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(currentCompletedTaskQueryVariables)) {
|
|
||||||
removeFromActivitiesQueries({
|
|
||||||
activityIdToRemove: viewableActivityId,
|
|
||||||
targetableObjects: [objectShowPageTargetableObject],
|
|
||||||
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
|
|
||||||
activitiesOrderByVariables:
|
|
||||||
currentCompletedTaskQueryVariables?.orderBy,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefined(currentIncompleteTaskQueryVariables)) {
|
|
||||||
removeFromActivitiesQueries({
|
|
||||||
activityIdToRemove: viewableActivityId,
|
|
||||||
targetableObjects: [objectShowPageTargetableObject],
|
|
||||||
activitiesFilters: currentIncompleteTaskQueryVariables?.filter,
|
|
||||||
activitiesOrderByVariables:
|
|
||||||
currentIncompleteTaskQueryVariables?.orderBy,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefined(currentNotesQueryVariables)) {
|
|
||||||
removeFromActivitiesQueries({
|
|
||||||
activityIdToRemove: viewableActivityId,
|
|
||||||
targetableObjects: [objectShowPageTargetableObject],
|
|
||||||
activitiesFilters: currentNotesQueryVariables?.filter,
|
|
||||||
activitiesOrderByVariables: currentNotesQueryVariables?.orderBy,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFromActivityTargetsQueries({
|
|
||||||
activityTargetsToRemove: activityInDrawer?.activityTargets ?? [],
|
|
||||||
targetableObjects: [objectShowPageTargetableObject],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNonEmptyArray(activityTargetIdsToDelete)) {
|
|
||||||
await deleteManyActivityTargets(activityTargetIdsToDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteOneActivity?.(viewableActivityId);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
const activity = snapshot
|
||||||
};
|
.getLoadable(recordStoreFamilyState(activityIdInDrawer))
|
||||||
|
.getValue() as Activity;
|
||||||
|
|
||||||
|
const activityTargets = getChildRelationArray({
|
||||||
|
childRelation: activity.activityTargets,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsRightDrawerOpen(false);
|
||||||
|
|
||||||
|
if (viewableActivityId) {
|
||||||
|
if (isActivityInCreateMode && isDefined(temporaryActivityForEditor)) {
|
||||||
|
deleteActivityFromCache(temporaryActivityForEditor);
|
||||||
|
setTemporaryActivityForEditor(null);
|
||||||
|
} else {
|
||||||
|
if (activityIdInDrawer) {
|
||||||
|
const activityTargetIdsToDelete: string[] =
|
||||||
|
activityTargets.map(mapToRecordId) ?? [];
|
||||||
|
|
||||||
|
if (weAreOnTaskPage) {
|
||||||
|
removeFromActivitiesQueries({
|
||||||
|
activityIdToRemove: viewableActivityId,
|
||||||
|
targetableObjects: [],
|
||||||
|
activitiesFilters: currentCompletedTaskQueryVariables?.filter,
|
||||||
|
activitiesOrderByVariables:
|
||||||
|
currentCompletedTaskQueryVariables?.orderBy,
|
||||||
|
});
|
||||||
|
|
||||||
|
removeFromActivitiesQueries({
|
||||||
|
activityIdToRemove: viewableActivityId,
|
||||||
|
targetableObjects: [],
|
||||||
|
activitiesFilters:
|
||||||
|
currentIncompleteTaskQueryVariables?.filter,
|
||||||
|
activitiesOrderByVariables:
|
||||||
|
currentIncompleteTaskQueryVariables?.orderBy,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
weAreOnObjectShowPage &&
|
||||||
|
isDefined(objectShowPageTargetableObject)
|
||||||
|
) {
|
||||||
|
removeFromActivitiesQueries({
|
||||||
|
activityIdToRemove: viewableActivityId,
|
||||||
|
targetableObjects: [objectShowPageTargetableObject],
|
||||||
|
activitiesFilters: {},
|
||||||
|
activitiesOrderByVariables:
|
||||||
|
FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDefined(currentCompletedTaskQueryVariables)) {
|
||||||
|
removeFromActivitiesQueries({
|
||||||
|
activityIdToRemove: viewableActivityId,
|
||||||
|
targetableObjects: [objectShowPageTargetableObject],
|
||||||
|
activitiesFilters:
|
||||||
|
currentCompletedTaskQueryVariables?.filter,
|
||||||
|
activitiesOrderByVariables:
|
||||||
|
currentCompletedTaskQueryVariables?.orderBy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(currentIncompleteTaskQueryVariables)) {
|
||||||
|
removeFromActivitiesQueries({
|
||||||
|
activityIdToRemove: viewableActivityId,
|
||||||
|
targetableObjects: [objectShowPageTargetableObject],
|
||||||
|
activitiesFilters:
|
||||||
|
currentIncompleteTaskQueryVariables?.filter,
|
||||||
|
activitiesOrderByVariables:
|
||||||
|
currentIncompleteTaskQueryVariables?.orderBy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(currentNotesQueryVariables)) {
|
||||||
|
removeFromActivitiesQueries({
|
||||||
|
activityIdToRemove: viewableActivityId,
|
||||||
|
targetableObjects: [objectShowPageTargetableObject],
|
||||||
|
activitiesFilters: currentNotesQueryVariables?.filter,
|
||||||
|
activitiesOrderByVariables:
|
||||||
|
currentNotesQueryVariables?.orderBy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromActivityTargetsQueries({
|
||||||
|
activityTargetsToRemove: activity?.activityTargets ?? [],
|
||||||
|
targetableObjects: [objectShowPageTargetableObject],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNonEmptyArray(activityTargetIdsToDelete)) {
|
||||||
|
await deleteManyActivityTargets(activityTargetIdsToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteOneActivity?.(viewableActivityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
activityIdInDrawer,
|
||||||
|
currentCompletedTaskQueryVariables,
|
||||||
|
currentIncompleteTaskQueryVariables,
|
||||||
|
currentNotesQueryVariables,
|
||||||
|
deleteActivityFromCache,
|
||||||
|
deleteManyActivityTargets,
|
||||||
|
deleteOneActivity,
|
||||||
|
isActivityInCreateMode,
|
||||||
|
objectShowPageTargetableObject,
|
||||||
|
removeFromActivitiesQueries,
|
||||||
|
removeFromActivityTargetsQueries,
|
||||||
|
setTemporaryActivityForEditor,
|
||||||
|
temporaryActivityForEditor,
|
||||||
|
viewableActivityId,
|
||||||
|
weAreOnObjectShowPage,
|
||||||
|
weAreOnTaskPage,
|
||||||
|
setIsRightDrawerOpen,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const record = useRecoilValue(
|
const record = useRecoilValue(
|
||||||
recordStoreFamilyState(viewableActivityId ?? ''),
|
recordStoreFamilyState(viewableActivityId ?? ''),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ActivityEditor } from '@/activities/components/ActivityEditor';
|
import { ActivityEditor } from '@/activities/components/ActivityEditor';
|
||||||
import { useActivityById } from '@/activities/hooks/useActivityById';
|
import { ActivityEditorEffect } from '@/activities/components/ActivityEditorEffect';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -24,18 +24,11 @@ export const RightDrawerActivity = ({
|
|||||||
showComment = true,
|
showComment = true,
|
||||||
fillTitleFromBody = false,
|
fillTitleFromBody = false,
|
||||||
}: RightDrawerActivityProps) => {
|
}: RightDrawerActivityProps) => {
|
||||||
const { activity, loading } = useActivityById({
|
|
||||||
activityId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!activity || loading) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
|
<ActivityEditorEffect activityId={activityId} />
|
||||||
<ActivityEditor
|
<ActivityEditor
|
||||||
activity={activity}
|
activityId={activityId}
|
||||||
showComment={showComment}
|
showComment={showComment}
|
||||||
fillTitleFromBody={fillTitleFromBody}
|
fillTitleFromBody={fillTitleFromBody}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
export const activityBodyFamilyState = atomFamily<
|
||||||
|
string,
|
||||||
|
{ activityId: string }
|
||||||
|
>({
|
||||||
|
key: 'activityBodyFamilyState',
|
||||||
|
default: '',
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const activityIdInDrawerState = atom<string | null>({
|
||||||
|
key: 'activityIdInDrawerState',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { atom } from 'recoil';
|
|
||||||
|
|
||||||
import { Activity } from '@/activities/types/Activity';
|
|
||||||
|
|
||||||
export const activityInDrawerState = atom<Activity | null>({
|
|
||||||
key: 'activityInDrawerState',
|
|
||||||
default: null,
|
|
||||||
});
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
export const activityTitleFamilyState = atomFamily<
|
||||||
|
string,
|
||||||
|
{ activityId: string }
|
||||||
|
>({
|
||||||
|
key: 'activityTitleFamilyState',
|
||||||
|
default: '',
|
||||||
|
});
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { currentUserDueTaskCountState } from '@/activities/tasks/states/currentUserTaskCountState';
|
||||||
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
import { parseDate } from '~/utils/date-utils';
|
||||||
|
|
||||||
|
export const CurrentUserDueTaskCountEffect = () => {
|
||||||
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
|
const [currentUserDueTaskCount, setCurrentUserDueTaskCount] = useRecoilState(
|
||||||
|
currentUserDueTaskCountState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { records: tasks } = useFindManyRecords({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||||
|
filter: {
|
||||||
|
type: { eq: 'Task' },
|
||||||
|
completedAt: { is: 'NULL' },
|
||||||
|
assigneeId: { eq: currentWorkspaceMember?.id },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const computedCurrentUserDueTaskCount = tasks.filter((task) => {
|
||||||
|
if (!task.dueAt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const dueDate = parseDate(task.dueAt).toJSDate();
|
||||||
|
const today = DateTime.now().endOf('day').toJSDate();
|
||||||
|
return dueDate <= today;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUserDueTaskCount !== computedCurrentUserDueTaskCount) {
|
||||||
|
setCurrentUserDueTaskCount(computedCurrentUserDueTaskCount);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
computedCurrentUserDueTaskCount,
|
||||||
|
currentUserDueTaskCount,
|
||||||
|
setCurrentUserDueTaskCount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -85,7 +85,7 @@ export const TaskRow = ({ task }: { task: Activity }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openActivityRightDrawer(task);
|
openActivityRightDrawer(task.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const currentUserDueTaskCountState = atom<number>({
|
||||||
|
default: 0,
|
||||||
|
key: 'currentUserDueTaskCountState',
|
||||||
|
});
|
||||||
@ -1,11 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useActivities } from '@/activities/hooks/useActivities';
|
|
||||||
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
||||||
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
|
import { timelineActivitiesNetworkingState } from '@/activities/timeline/states/timelineActivitiesNetworkingState';
|
||||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
|
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
|
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
|
||||||
import {
|
import {
|
||||||
@ -15,7 +12,6 @@ import {
|
|||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
|
||||||
|
|
||||||
import { TimelineItemsContainer } from './TimelineItemsContainer';
|
import { TimelineItemsContainer } from './TimelineItemsContainer';
|
||||||
|
|
||||||
@ -36,21 +32,10 @@ export const Timeline = ({
|
|||||||
}: {
|
}: {
|
||||||
targetableObject: ActivityTargetableObject;
|
targetableObject: ActivityTargetableObject;
|
||||||
}) => {
|
}) => {
|
||||||
const { activities, initialized, noActivities } = useActivities({
|
const { initialized, noActivities } = useRecoilValue(
|
||||||
targetableObjects: [targetableObject],
|
timelineActivitiesNetworkingState,
|
||||||
activitiesFilters: {},
|
|
||||||
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
|
||||||
skip: !isDefined(targetableObject),
|
|
||||||
});
|
|
||||||
|
|
||||||
const setTimelineTargetableObject = useSetRecoilState(
|
|
||||||
objectShowPageTargetableObjectState,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimelineTargetableObject(targetableObject);
|
|
||||||
}, [targetableObject, setTimelineTargetableObject]);
|
|
||||||
|
|
||||||
const showEmptyState = noActivities;
|
const showEmptyState = noActivities;
|
||||||
|
|
||||||
const showLoadingState = !initialized;
|
const showLoadingState = !initialized;
|
||||||
@ -79,7 +64,7 @@ export const Timeline = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMainContainer>
|
<StyledMainContainer>
|
||||||
<TimelineItemsContainer activities={activities} />
|
<TimelineItemsContainer />
|
||||||
</StyledMainContainer>
|
</StyledMainContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { timelineActivityWithoutTargetsFamilyState } from '@/activities/timeline/states/timelineActivityFirstLevelFamilySelector';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
@ -136,28 +136,42 @@ const StyledTimelineItemContainer = styled.div<{ isGap?: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type TimelineActivityProps = {
|
type TimelineActivityProps = {
|
||||||
activity: Activity;
|
|
||||||
isLastActivity?: boolean;
|
isLastActivity?: boolean;
|
||||||
|
activityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TimelineActivity = ({
|
export const TimelineActivity = ({
|
||||||
activity,
|
|
||||||
isLastActivity,
|
isLastActivity,
|
||||||
|
activityId,
|
||||||
}: TimelineActivityProps) => {
|
}: TimelineActivityProps) => {
|
||||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
|
const activityForTimeline = useRecoilValue(
|
||||||
const exactCreatedAt = beautifyExactDateTime(activity.createdAt);
|
timelineActivityWithoutTargetsFamilyState(activityId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const beautifiedCreatedAt = activityForTimeline
|
||||||
|
? beautifyPastDateRelativeToNow(activityForTimeline.createdAt)
|
||||||
|
: '';
|
||||||
|
const exactCreatedAt = activityForTimeline
|
||||||
|
? beautifyExactDateTime(activityForTimeline.createdAt)
|
||||||
|
: '';
|
||||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const activityFromStore = useRecoilValue(recordStoreFamilyState(activity.id));
|
const activityFromStore = useRecoilValue(
|
||||||
|
recordStoreFamilyState(activityForTimeline?.id ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!activityForTimeline) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledTimelineItemContainer>
|
<StyledTimelineItemContainer>
|
||||||
<StyledAvatarContainer>
|
<StyledAvatarContainer>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={activity.author?.avatarUrl}
|
avatarUrl={activityForTimeline.author?.avatarUrl}
|
||||||
placeholder={activity.author?.name.firstName ?? ''}
|
placeholder={activityForTimeline.author?.name.firstName ?? ''}
|
||||||
size="sm"
|
size="sm"
|
||||||
type="rounded"
|
type="rounded"
|
||||||
/>
|
/>
|
||||||
@ -166,23 +180,26 @@ export const TimelineActivity = ({
|
|||||||
<StyledItemTitleContainer>
|
<StyledItemTitleContainer>
|
||||||
<StyledItemAuthorText>
|
<StyledItemAuthorText>
|
||||||
<span>
|
<span>
|
||||||
{activity.author?.name.firstName}{' '}
|
{activityForTimeline.author?.name.firstName}{' '}
|
||||||
{activity.author?.name.lastName}
|
{activityForTimeline.author?.name.lastName}
|
||||||
</span>
|
</span>
|
||||||
created a {activity.type.toLowerCase()}
|
created a {activityForTimeline.type.toLowerCase()}
|
||||||
</StyledItemAuthorText>
|
</StyledItemAuthorText>
|
||||||
<StyledItemTitle>
|
<StyledItemTitle>
|
||||||
<StyledIconContainer>
|
<StyledIconContainer>
|
||||||
{activity.type === 'Note' && (
|
{activityForTimeline.type === 'Note' && (
|
||||||
<IconNotes size={theme.icon.size.sm} />
|
<IconNotes size={theme.icon.size.sm} />
|
||||||
)}
|
)}
|
||||||
{activity.type === 'Task' && (
|
{activityForTimeline.type === 'Task' && (
|
||||||
<IconCheckbox size={theme.icon.size.sm} />
|
<IconCheckbox size={theme.icon.size.sm} />
|
||||||
)}
|
)}
|
||||||
</StyledIconContainer>
|
</StyledIconContainer>
|
||||||
{(activity.type === 'Note' || activity.type === 'Task') && (
|
{(activityForTimeline.type === 'Note' ||
|
||||||
|
activityForTimeline.type === 'Task') && (
|
||||||
<StyledActivityTitle
|
<StyledActivityTitle
|
||||||
onClick={() => openActivityRightDrawer(activity)}
|
onClick={() =>
|
||||||
|
openActivityRightDrawer(activityForTimeline.id)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
“
|
“
|
||||||
<StyledActivityLink
|
<StyledActivityLink
|
||||||
@ -195,11 +212,11 @@ export const TimelineActivity = ({
|
|||||||
)}
|
)}
|
||||||
</StyledItemTitle>
|
</StyledItemTitle>
|
||||||
</StyledItemTitleContainer>
|
</StyledItemTitleContainer>
|
||||||
<StyledItemTitleDate id={`id-${activity.id}`}>
|
<StyledItemTitleDate id={`id-${activityForTimeline.id}`}>
|
||||||
{beautifiedCreatedAt}
|
{beautifiedCreatedAt}
|
||||||
</StyledItemTitleDate>
|
</StyledItemTitleDate>
|
||||||
<StyledTooltip
|
<StyledTooltip
|
||||||
anchorSelect={`#id-${activity.id}`}
|
anchorSelect={`#id-${activityForTimeline.id}`}
|
||||||
content={exactCreatedAt}
|
content={exactCreatedAt}
|
||||||
clickable
|
clickable
|
||||||
noArrow
|
noArrow
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
|
||||||
import { groupActivitiesByMonth } from '../utils/groupActivitiesByMonth';
|
import { groupActivitiesByMonth } from '../utils/groupActivitiesByMonth';
|
||||||
@ -23,14 +24,12 @@ const StyledTimelineContainer = styled.div`
|
|||||||
|
|
||||||
const StyledScrollWrapper = styled(ScrollWrapper)``;
|
const StyledScrollWrapper = styled(ScrollWrapper)``;
|
||||||
|
|
||||||
export type TimelineItemsContainerProps = {
|
export const TimelineItemsContainer = () => {
|
||||||
activities: ActivityForDrawer[];
|
const timelineActivitiesForGroup = useRecoilValue(
|
||||||
};
|
timelineActivitiesForGroupState,
|
||||||
|
);
|
||||||
|
|
||||||
export const TimelineItemsContainer = ({
|
const groupedActivities = groupActivitiesByMonth(timelineActivitiesForGroup);
|
||||||
activities,
|
|
||||||
}: TimelineItemsContainerProps) => {
|
|
||||||
const groupedActivities = groupActivitiesByMonth(activities);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledScrollWrapper>
|
<StyledScrollWrapper>
|
||||||
|
|||||||
@ -0,0 +1,131 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useActivities } from '@/activities/hooks/useActivities';
|
||||||
|
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
|
||||||
|
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
|
||||||
|
import { timelineActivitiesFammilyState } from '@/activities/timeline/states/timelineActivitiesFamilyState';
|
||||||
|
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
|
||||||
|
import { timelineActivitiesNetworkingState } from '@/activities/timeline/states/timelineActivitiesNetworkingState';
|
||||||
|
import { timelineActivityWithoutTargetsFamilyState } from '@/activities/timeline/states/timelineActivityFirstLevelFamilySelector';
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
import { sortObjectRecordByDateField } from '@/object-record/utils/sortObjectRecordByDateField';
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const TimelineQueryEffect = ({
|
||||||
|
targetableObject,
|
||||||
|
}: {
|
||||||
|
targetableObject: ActivityTargetableObject;
|
||||||
|
}) => {
|
||||||
|
const setTimelineTargetableObject = useSetRecoilState(
|
||||||
|
objectShowPageTargetableObjectState,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimelineTargetableObject(targetableObject);
|
||||||
|
}, [targetableObject, setTimelineTargetableObject]);
|
||||||
|
|
||||||
|
const { activities, initialized, noActivities } = useActivities({
|
||||||
|
targetableObjects: [targetableObject],
|
||||||
|
activitiesFilters: {},
|
||||||
|
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
||||||
|
skip: !isDefined(targetableObject),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [timelineActivitiesNetworking, setTimelineActivitiesNetworking] =
|
||||||
|
useRecoilState(timelineActivitiesNetworkingState);
|
||||||
|
|
||||||
|
const [timelineActivitiesForGroup, setTimelineActivitiesForGroup] =
|
||||||
|
useRecoilState(timelineActivitiesForGroupState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDefined(targetableObject)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activitiesForGroup = activities
|
||||||
|
.map((activity) => ({
|
||||||
|
id: activity.id,
|
||||||
|
createdAt: activity.createdAt,
|
||||||
|
}))
|
||||||
|
.toSorted(sortObjectRecordByDateField('createdAt', 'DescNullsLast'));
|
||||||
|
|
||||||
|
const timelineActivitiesForGroupSorted =
|
||||||
|
timelineActivitiesForGroup.toSorted(
|
||||||
|
sortObjectRecordByDateField('createdAt', 'DescNullsLast'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDeeplyEqual(activitiesForGroup, timelineActivitiesForGroupSorted)) {
|
||||||
|
setTimelineActivitiesForGroup(activitiesForGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isDeeplyEqual(timelineActivitiesNetworking.initialized, initialized) ||
|
||||||
|
!isDeeplyEqual(timelineActivitiesNetworking.noActivities, noActivities)
|
||||||
|
) {
|
||||||
|
setTimelineActivitiesNetworking({
|
||||||
|
initialized,
|
||||||
|
noActivities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
activities,
|
||||||
|
initialized,
|
||||||
|
noActivities,
|
||||||
|
setTimelineActivitiesNetworking,
|
||||||
|
targetableObject,
|
||||||
|
timelineActivitiesNetworking,
|
||||||
|
timelineActivitiesForGroup,
|
||||||
|
setTimelineActivitiesForGroup,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const updateTimelineActivities = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
(newActivities: Activity[]) => {
|
||||||
|
for (const newActivity of newActivities) {
|
||||||
|
const currentActivity = snapshot
|
||||||
|
.getLoadable(timelineActivitiesFammilyState(newActivity.id))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (!isDeeplyEqual(newActivity, currentActivity)) {
|
||||||
|
set(timelineActivitiesFammilyState(newActivity.id), newActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentActivityWithoutTarget = snapshot
|
||||||
|
.getLoadable(
|
||||||
|
timelineActivityWithoutTargetsFamilyState(newActivity.id),
|
||||||
|
)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const newActivityWithoutTarget = {
|
||||||
|
id: newActivity.id,
|
||||||
|
title: newActivity.title,
|
||||||
|
createdAt: newActivity.createdAt,
|
||||||
|
author: newActivity.author,
|
||||||
|
type: newActivity.type,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isDeeplyEqual(
|
||||||
|
newActivityWithoutTarget,
|
||||||
|
currentActivityWithoutTarget,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
set(
|
||||||
|
timelineActivityWithoutTargetsFamilyState(newActivity.id),
|
||||||
|
newActivityWithoutTarget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateTimelineActivities(activities);
|
||||||
|
}, [activities, updateTimelineActivities]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -68,7 +68,7 @@ export const TimelineActivityGroup = ({
|
|||||||
{group.items.map((activity, index) => (
|
{group.items.map((activity, index) => (
|
||||||
<TimelineActivity
|
<TimelineActivity
|
||||||
key={activity.id}
|
key={activity.id}
|
||||||
activity={activity}
|
activityId={activity.id}
|
||||||
isLastActivity={index === group.items.length - 1}
|
isLastActivity={index === group.items.length - 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useRecoilCallback, useRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||||
import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
|
import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
|
||||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
|
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
|
||||||
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
|
||||||
|
export const timelineActivitiesFammilyState = atomFamily<
|
||||||
|
Activity | null,
|
||||||
|
string
|
||||||
|
>({
|
||||||
|
key: 'timelineActivitiesFammilyState',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
import { ActivityForActivityGroup } from '@/activities/timeline/utils/groupActivitiesByMonth';
|
||||||
|
|
||||||
|
export const timelineActivitiesForGroupState = atom<ActivityForActivityGroup[]>(
|
||||||
|
{
|
||||||
|
key: 'timelineActivitiesForGroupState',
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const timelineActivitiesNetworkingState = atom<{
|
||||||
|
initialized: boolean;
|
||||||
|
noActivities: boolean;
|
||||||
|
}>({
|
||||||
|
key: 'timelineActivitiesNetworkingState',
|
||||||
|
default: {
|
||||||
|
initialized: false,
|
||||||
|
noActivities: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
|
||||||
|
export const timelineActivityWithoutTargetsFamilyState = atomFamily<
|
||||||
|
Pick<Activity, 'id' | 'title' | 'createdAt' | 'author' | 'type'> | null,
|
||||||
|
string
|
||||||
|
>({
|
||||||
|
key: 'timelineActivityFirstLevelFamilySelector',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -1,13 +1,18 @@
|
|||||||
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
|
||||||
export interface ActivityGroup {
|
export type ActivityForActivityGroup = Pick<Activity, 'id' | 'createdAt'>;
|
||||||
|
|
||||||
|
export type ActivityGroup = {
|
||||||
month: number;
|
month: number;
|
||||||
year: number;
|
year: number;
|
||||||
items: ActivityForDrawer[];
|
items: ActivityForActivityGroup[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export const groupActivitiesByMonth = (activities: ActivityForDrawer[]) => {
|
export const groupActivitiesByMonth = (
|
||||||
|
activities: ActivityForActivityGroup[],
|
||||||
|
) => {
|
||||||
const acitivityGroups: ActivityGroup[] = [];
|
const acitivityGroups: ActivityGroup[] = [];
|
||||||
|
|
||||||
for (const activity of activities) {
|
for (const activity of activities) {
|
||||||
const d = new Date(activity.createdAt);
|
const d = new Date(activity.createdAt);
|
||||||
const month = d.getMonth();
|
const month = d.getMonth();
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|
||||||
|
|
||||||
export type ActivityTargetableObject = {
|
export type ActivityTargetableObject = {
|
||||||
id: string;
|
id: string;
|
||||||
targetObjectNameSingular: string;
|
targetObjectNameSingular: string;
|
||||||
targetObjectRecord: ObjectRecord;
|
|
||||||
relatedTargetableObjects?: ActivityTargetableObject[];
|
relatedTargetableObjects?: ActivityTargetableObject[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,13 +4,16 @@ import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
|||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { flattenTargetableObjectsAndTheirRelatedTargetableObjects } from '@/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects';
|
import { flattenTargetableObjectsAndTheirRelatedTargetableObjects } from '@/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects';
|
||||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
export const getActivityTargetsToCreateFromTargetableObjects = ({
|
export const makeActivityTargetsToCreateFromTargetableObjects = ({
|
||||||
targetableObjects,
|
targetableObjects,
|
||||||
activityId,
|
activityId,
|
||||||
|
targetObjectRecords,
|
||||||
}: {
|
}: {
|
||||||
targetableObjects: ActivityTargetableObject[];
|
targetableObjects: ActivityTargetableObject[];
|
||||||
activityId: string;
|
activityId: string;
|
||||||
|
targetObjectRecords: ObjectRecord[];
|
||||||
}): Partial<ActivityTarget>[] => {
|
}): Partial<ActivityTarget>[] => {
|
||||||
const activityTargetableObjects = targetableObjects
|
const activityTargetableObjects = targetableObjects
|
||||||
? flattenTargetableObjectsAndTheirRelatedTargetableObjects(
|
? flattenTargetableObjectsAndTheirRelatedTargetableObjects(
|
||||||
@ -24,9 +27,12 @@ export const getActivityTargetsToCreateFromTargetableObjects = ({
|
|||||||
nameSingular: targetableObject.targetObjectNameSingular,
|
nameSingular: targetableObject.targetObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const relatedObjectRecord = targetObjectRecords.find(
|
||||||
|
(record) => record.id === targetableObject.id,
|
||||||
|
);
|
||||||
|
|
||||||
const activityTarget = {
|
const activityTarget = {
|
||||||
[targetableObject.targetObjectNameSingular]:
|
[targetableObject.targetObjectNameSingular]: relatedObjectRecord,
|
||||||
targetableObject.targetObjectRecord,
|
|
||||||
[targetableObjectFieldIdName]: targetableObject.id,
|
[targetableObjectFieldIdName]: targetableObject.id,
|
||||||
activityId,
|
activityId,
|
||||||
id: v4(),
|
id: v4(),
|
||||||
|
|||||||
@ -196,7 +196,7 @@ export const CommandMenu = () => {
|
|||||||
id: activity.id,
|
id: activity.id,
|
||||||
label: activity.title ?? '',
|
label: activity.title ?? '',
|
||||||
to: '',
|
to: '',
|
||||||
onCommandClick: () => openActivityRightDrawer(activity),
|
onCommandClick: () => openActivityRightDrawer(activity.id),
|
||||||
})),
|
})),
|
||||||
[activities, openActivityRightDrawer],
|
[activities, openActivityRightDrawer],
|
||||||
);
|
);
|
||||||
@ -372,7 +372,7 @@ export const CommandMenu = () => {
|
|||||||
Icon={IconNotes}
|
Icon={IconNotes}
|
||||||
key={activity.id}
|
key={activity.id}
|
||||||
label={activity.title ?? ''}
|
label={activity.title ?? ''}
|
||||||
onClick={() => openActivityRightDrawer(activity)}
|
onClick={() => openActivityRightDrawer(activity.id)}
|
||||||
/>
|
/>
|
||||||
</SelectableItem>
|
</SelectableItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { useCurrentUserTaskCount } from '@/activities/tasks/hooks/useCurrentUserDueTaskCount';
|
import { CurrentUserDueTaskCountEffect } from '@/activities/tasks/components/CurrentUserDueTaskCountEffect';
|
||||||
|
import { currentUserDueTaskCountState } from '@/activities/tasks/states/currentUserTaskCountState';
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { Favorites } from '@/favorites/components/Favorites';
|
import { Favorites } from '@/favorites/components/Favorites';
|
||||||
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
|
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
|
||||||
@ -23,7 +24,7 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const { toggleCommandMenu } = useCommandMenu();
|
const { toggleCommandMenu } = useCommandMenu();
|
||||||
const isTasksPage = useIsTasksPage();
|
const isTasksPage = useIsTasksPage();
|
||||||
const { currentUserDueTaskCount } = useCurrentUserTaskCount();
|
const currentUserDueTaskCount = useRecoilValue(currentUserDueTaskCountState);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const setNavigationMemorizedUrl = useSetRecoilState(
|
const setNavigationMemorizedUrl = useSetRecoilState(
|
||||||
@ -54,6 +55,7 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
}}
|
}}
|
||||||
Icon={IconSettings}
|
Icon={IconSettings}
|
||||||
/>
|
/>
|
||||||
|
<CurrentUserDueTaskCountEffect />
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerItem
|
||||||
label="Tasks"
|
label="Tasks"
|
||||||
to="/tasks"
|
to="/tasks"
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||||
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
|
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import {
|
import {
|
||||||
FieldContext,
|
FieldContext,
|
||||||
@ -16,7 +14,9 @@ import { RecordInlineCell } from '@/object-record/record-inline-cell/components/
|
|||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { RecordRelationFieldCardSection } from '@/object-record/record-relation-card/components/RecordRelationFieldCardSection';
|
import { RecordRelationFieldCardSection } from '@/object-record/record-relation-card/components/RecordRelationFieldCardSection';
|
||||||
|
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
||||||
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
|
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
|
||||||
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
||||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
@ -29,6 +29,7 @@ import {
|
|||||||
FileFolder,
|
FileFolder,
|
||||||
useUploadImageMutation,
|
useUploadImageMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
type RecordShowContainerProps = {
|
type RecordShowContainerProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -39,28 +40,25 @@ export const RecordShowContainer = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
}: RecordShowContainerProps) => {
|
}: RecordShowContainerProps) => {
|
||||||
const {
|
const { objectMetadataItem, labelIdentifierFieldMetadata } =
|
||||||
objectMetadataItem,
|
useObjectMetadataItem({
|
||||||
labelIdentifierFieldMetadata,
|
objectNameSingular,
|
||||||
mapToObjectRecordIdentifier,
|
});
|
||||||
} = useObjectMetadataItem({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setEntityFields = useSetRecoilState(
|
const [recordLoading] = useRecoilState(
|
||||||
|
recordLoadingFamilyState(objectRecordId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [recordFromStore] = useRecoilState(
|
||||||
recordStoreFamilyState(objectRecordId),
|
recordStoreFamilyState(objectRecordId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { record, loading } = useFindOneRecord({
|
const recordIdentifier = useRecoilValue(
|
||||||
objectRecordId,
|
recordStoreIdentifierFamilySelector({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
depth: 3,
|
recordId: objectRecordId,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
useEffect(() => {
|
|
||||||
if (!record) return;
|
|
||||||
setEntityFields(record);
|
|
||||||
}, [record, setEntityFields]);
|
|
||||||
|
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
|
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
|
||||||
@ -96,12 +94,12 @@ export const RecordShowContainer = ({
|
|||||||
if (!updateOneRecord) {
|
if (!updateOneRecord) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!record) {
|
if (!recordFromStore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateOneRecord({
|
await updateOneRecord({
|
||||||
idToUpdate: record.id,
|
idToUpdate: objectRecordId,
|
||||||
updateOneRecordInput: {
|
updateOneRecordInput: {
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
@ -132,23 +130,19 @@ export const RecordShowContainer = ({
|
|||||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||||
<ShowPageContainer>
|
<ShowPageContainer>
|
||||||
<ShowPageLeftContainer>
|
<ShowPageLeftContainer>
|
||||||
{!loading && !!record && (
|
{!recordLoading && isDefined(recordFromStore) && (
|
||||||
<>
|
<>
|
||||||
<ShowPageSummaryCard
|
<ShowPageSummaryCard
|
||||||
id={record.id}
|
id={objectRecordId}
|
||||||
logoOrAvatar={
|
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
|
||||||
mapToObjectRecordIdentifier(record).avatarUrl ?? ''
|
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
||||||
}
|
date={recordFromStore.createdAt ?? ''}
|
||||||
avatarPlaceholder={
|
|
||||||
mapToObjectRecordIdentifier(record).name ?? ''
|
|
||||||
}
|
|
||||||
date={record.createdAt ?? ''}
|
|
||||||
title={
|
title={
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
value={{
|
value={{
|
||||||
entityId: record.id,
|
entityId: objectRecordId,
|
||||||
recoilScopeId:
|
recoilScopeId:
|
||||||
record.id + labelIdentifierFieldMetadata?.id,
|
objectRecordId + labelIdentifierFieldMetadata?.id,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
fieldDefinition: {
|
fieldDefinition: {
|
||||||
type: parseFieldType(
|
type: parseFieldType(
|
||||||
@ -169,9 +163,7 @@ export const RecordShowContainer = ({
|
|||||||
<RecordInlineCell />
|
<RecordInlineCell />
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
}
|
}
|
||||||
avatarType={
|
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
|
||||||
mapToObjectRecordIdentifier(record).avatarType ?? 'rounded'
|
|
||||||
}
|
|
||||||
onUploadPicture={
|
onUploadPicture={
|
||||||
objectNameSingular === 'person' ? onUploadPicture : undefined
|
objectNameSingular === 'person' ? onUploadPicture : undefined
|
||||||
}
|
}
|
||||||
@ -179,11 +171,11 @@ export const RecordShowContainer = ({
|
|||||||
<PropertyBox extraPadding={true}>
|
<PropertyBox extraPadding={true}>
|
||||||
{inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
{inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
key={record.id + fieldMetadataItem.id}
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
value={{
|
value={{
|
||||||
entityId: record.id,
|
entityId: objectRecordId,
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
recoilScopeId: record.id + fieldMetadataItem.id,
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
fieldDefinition:
|
fieldDefinition:
|
||||||
formatFieldMetadataItemAsColumnDefinition({
|
formatFieldMetadataItemAsColumnDefinition({
|
||||||
@ -217,10 +209,10 @@ export const RecordShowContainer = ({
|
|||||||
})
|
})
|
||||||
.map((fieldMetadataItem, index) => (
|
.map((fieldMetadataItem, index) => (
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
key={record.id + fieldMetadataItem.id}
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
value={{
|
value={{
|
||||||
entityId: record.id,
|
entityId: objectRecordId,
|
||||||
recoilScopeId: record.id + fieldMetadataItem.id,
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
fieldDefinition:
|
fieldDefinition:
|
||||||
formatFieldMetadataItemAsColumnDefinition({
|
formatFieldMetadataItemAsColumnDefinition({
|
||||||
@ -238,12 +230,11 @@ export const RecordShowContainer = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ShowPageLeftContainer>
|
</ShowPageLeftContainer>
|
||||||
{record ? (
|
{recordFromStore ? (
|
||||||
<ShowPageRightContainer
|
<ShowPageRightContainer
|
||||||
targetableObject={{
|
targetableObject={{
|
||||||
id: record.id,
|
id: objectRecordId,
|
||||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||||
targetObjectRecord: record,
|
|
||||||
}}
|
}}
|
||||||
timeline
|
timeline
|
||||||
tasks
|
tasks
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useActivityConnectionUtils } from '@/activities/hooks/useActivityConnectionUtils';
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
|
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const RecordShowContainer = ({
|
||||||
|
objectRecordId,
|
||||||
|
objectNameSingular,
|
||||||
|
}: {
|
||||||
|
objectRecordId: string;
|
||||||
|
objectNameSingular: string;
|
||||||
|
}) => {
|
||||||
|
const { record, loading } = useFindOneRecord({
|
||||||
|
objectRecordId,
|
||||||
|
objectNameSingular,
|
||||||
|
depth: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setRecordStore = useSetRecoilState(
|
||||||
|
recordStoreFamilyState(objectRecordId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [recordLoading, setRecordLoading] = useRecoilState(
|
||||||
|
recordLoadingFamilyState(objectRecordId),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loading !== recordLoading) {
|
||||||
|
setRecordLoading(loading);
|
||||||
|
}
|
||||||
|
}, [loading, recordLoading, setRecordLoading]);
|
||||||
|
|
||||||
|
const { makeActivityWithoutConnection } = useActivityConnectionUtils();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && isDefined(record)) {
|
||||||
|
const { activity: activityWithoutConnection } =
|
||||||
|
makeActivityWithoutConnection(record as any);
|
||||||
|
|
||||||
|
setRecordStore(activityWithoutConnection as Activity);
|
||||||
|
}
|
||||||
|
}, [loading, record, setRecordStore, makeActivityWithoutConnection]);
|
||||||
|
};
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
export const recordLoadingFamilyState = atomFamily<boolean, string>({
|
||||||
|
key: 'recordLoadingFamilyState',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { selectorFamily } from 'recoil';
|
||||||
|
|
||||||
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
|
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
|
||||||
|
export const recordStoreIdentifierFamilySelector = selectorFamily({
|
||||||
|
key: 'recordStoreIdentifierFamilySelector',
|
||||||
|
get:
|
||||||
|
({
|
||||||
|
recordId,
|
||||||
|
objectNameSingular,
|
||||||
|
}: {
|
||||||
|
recordId: string;
|
||||||
|
objectNameSingular: string;
|
||||||
|
}) =>
|
||||||
|
({ get }) => {
|
||||||
|
const recordFromStore = get(recordStoreFamilyState(recordId));
|
||||||
|
|
||||||
|
const objectMetadataItems = get(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const objectMetadataItem = objectMetadataItems.find(
|
||||||
|
(item) => item.nameSingular === objectNameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!objectMetadataItem || !recordFromStore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getObjectRecordIdentifier({
|
||||||
|
objectMetadataItem: objectMetadataItem,
|
||||||
|
record: recordFromStore,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const getChildRelationArray = ({
|
||||||
|
childRelation,
|
||||||
|
}: {
|
||||||
|
childRelation: any;
|
||||||
|
}) => {
|
||||||
|
if (isDefined(childRelation.edges) && Array.isArray(childRelation.edges)) {
|
||||||
|
return childRelation.edges.map((edge: ObjectRecordEdge) => edge.node);
|
||||||
|
} else {
|
||||||
|
return childRelation;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -6,6 +6,7 @@ import { Attachments } from '@/activities/files/components/Attachments';
|
|||||||
import { Notes } from '@/activities/notes/components/Notes';
|
import { Notes } from '@/activities/notes/components/Notes';
|
||||||
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
|
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
|
||||||
import { Timeline } from '@/activities/timeline/components/Timeline';
|
import { Timeline } from '@/activities/timeline/components/Timeline';
|
||||||
|
import { TimelineQueryEffect } from '@/activities/timeline/components/TimelineQueryEffect';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
@ -42,7 +43,10 @@ const StyledTabListContainer = styled.div`
|
|||||||
export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
|
export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
|
||||||
|
|
||||||
type ShowPageRightContainerProps = {
|
type ShowPageRightContainerProps = {
|
||||||
targetableObject: ActivityTargetableObject;
|
targetableObject: Pick<
|
||||||
|
ActivityTargetableObject,
|
||||||
|
'targetObjectNameSingular' | 'id'
|
||||||
|
>;
|
||||||
timeline?: boolean;
|
timeline?: boolean;
|
||||||
tasks?: boolean;
|
tasks?: boolean;
|
||||||
notes?: boolean;
|
notes?: boolean;
|
||||||
@ -114,7 +118,10 @@ export const ShowPageRightContainer = ({
|
|||||||
<TabList tabListId={TAB_LIST_COMPONENT_ID} tabs={TASK_TABS} />
|
<TabList tabListId={TAB_LIST_COMPONENT_ID} tabs={TASK_TABS} />
|
||||||
</StyledTabListContainer>
|
</StyledTabListContainer>
|
||||||
{activeTabId === 'timeline' && (
|
{activeTabId === 'timeline' && (
|
||||||
<Timeline targetableObject={targetableObject} />
|
<>
|
||||||
|
<TimelineQueryEffect targetableObject={targetableObject} />
|
||||||
|
<Timeline targetableObject={targetableObject} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{activeTabId === 'tasks' && (
|
{activeTabId === 'tasks' && (
|
||||||
<ObjectTasks targetableObject={targetableObject} />
|
<ObjectTasks targetableObject={targetableObject} />
|
||||||
|
|||||||
@ -97,7 +97,6 @@ export const RecordShowPage = () => {
|
|||||||
activityTargetObject={{
|
activityTargetObject={{
|
||||||
id: record.id,
|
id: record.id,
|
||||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||||
targetObjectRecord: record,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ShowPageMoreButton
|
<ShowPageMoreButton
|
||||||
|
|||||||
Reference in New Issue
Block a user