From 53e5d4b40c52882b541fa4596bd74a4e09300535 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:33:19 +0530 Subject: [PATCH] [WEB-1680] dev: issue detail activity revamp and issue detail page improvement (#5075) * chore: issue link activity message updated * chore: activity filter type constant added * dev: issue activity revamp and code refactor * chore: issue detail widget oreder updated in peek overview * chore: issue detail page padding improvement * fix: relation widget toast alert * fix: relation widget toast alert * fix: peek overview attachment delete modal * chore: code refactor * chore: code refactor * chore: code refactor * chore: code refactor * chore: issue detail sidebar parent field --- .../attachment/attachment-list-item.tsx | 14 +-- .../relations/content.tsx | 1 - .../issue-detail-widgets/relations/helper.tsx | 10 -- .../issue-activity/activity-comment-root.tsx | 25 +++- .../issue-activity/activity-filter.tsx | 83 +++++++++++++ .../issue-activity/activity/actions/link.tsx | 2 +- .../issue-activity/activity/activity-list.tsx | 4 +- .../issue-detail/issue-activity/index.ts | 1 + .../issue-detail/issue-activity/root.tsx | 113 ++++++------------ .../issues/issue-detail/main-content.tsx | 20 ++-- .../components/issues/issue-detail/root.tsx | 2 +- .../issues/issue-detail/sidebar.tsx | 19 ++- .../components/issues/peek-overview/view.tsx | 30 +++-- web/core/constants/issue.ts | 16 +++ 14 files changed, 208 insertions(+), 132 deletions(-) create mode 100644 web/core/components/issues/issue-detail/issue-activity/activity-filter.tsx diff --git a/web/core/components/issues/attachment/attachment-list-item.tsx b/web/core/components/issues/attachment/attachment-list-item.tsx index 0e36027b1a..6bdf4490dc 100644 --- a/web/core/components/issues/attachment/attachment-list-item.tsx +++ b/web/core/components/issues/attachment/attachment-list-item.tsx @@ -1,6 +1,6 @@ "use client"; -import { FC, useState } from "react"; +import { FC } from "react"; import { observer } from "mobx-react"; import { Trash } from "lucide-react"; // ui @@ -33,9 +33,9 @@ export const IssueAttachmentsListItem: FC = observer( const { getUserDetails } = useMember(); const { attachment: { getAttachmentById }, + isDeleteAttachmentModalOpen, + toggleDeleteAttachmentModal, } = useIssueDetail(); - // state - const [isDeleteIssueAttachmentModalOpen, setIsDeleteIssueAttachmentModalOpen] = useState(false); // derived values const attachment = attachmentId ? getAttachmentById(attachmentId) : undefined; @@ -46,10 +46,10 @@ export const IssueAttachmentsListItem: FC = observer( return ( <> - {isDeleteIssueAttachmentModalOpen && ( + {isDeleteAttachmentModalOpen && ( setIsDeleteIssueAttachmentModalOpen(false)} + isOpen={!!isDeleteAttachmentModalOpen} + onClose={() => toggleDeleteAttachmentModal(null)} handleAttachmentOperations={handleAttachmentOperations} data={attachment} /> @@ -95,7 +95,7 @@ export const IssueAttachmentsListItem: FC = observer( onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setIsDeleteIssueAttachmentModalOpen(true); + toggleDeleteAttachmentModal(attachmentId); }} >
diff --git a/web/core/components/issues/issue-detail-widgets/relations/content.tsx b/web/core/components/issues/issue-detail-widgets/relations/content.tsx index f4a58c7d17..b078c19d11 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/content.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/content.tsx @@ -160,7 +160,6 @@ export const RelationsCollapsibleContent: FC = observer((props) => { onSubmit={async () => await issueOperations.remove(workspaceSlug, projectId, issueCrudState?.delete?.issue?.id as string) } - isSubIssue /> )} diff --git a/web/core/components/issues/issue-detail-widgets/relations/helper.tsx b/web/core/components/issues/issue-detail-widgets/relations/helper.tsx index 940e884877..f2366f7d43 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/helper.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/helper.tsx @@ -71,22 +71,12 @@ export const useRelationOperations = (): TRelationIssueOperations => { remove: async (workspaceSlug: string, projectId: string, issueId: string) => { try { await removeIssue(workspaceSlug, projectId, issueId); - setToast({ - title: "Success!", - type: TOAST_TYPE.SUCCESS, - message: "Issue deleted successfully", - }); captureIssueEvent({ eventName: ISSUE_DELETED, payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, path: pathname, }); } catch (error) { - setToast({ - title: "Error!", - type: TOAST_TYPE.ERROR, - message: "Issue delete failed", - }); captureIssueEvent({ eventName: ISSUE_DELETED, payload: { id: issueId, state: "FAILED", element: "Issue detail page" }, diff --git a/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx b/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx index 4393bec47f..6a5263fd9c 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/activity-comment-root.tsx @@ -1,9 +1,10 @@ import { FC } from "react"; import { observer } from "mobx-react"; // hooks +import { EActivityFilterType } from "@/constants/issue"; import { useIssueDetail } from "@/hooks/store"; // components -import { IssueActivityList } from "./activity/activity-list"; +import { IssueActivityItem } from "./activity/activity-list"; import { IssueCommentCard } from "./comments/comment-card"; // types import { TActivityOperations } from "./root"; @@ -12,13 +13,15 @@ type TIssueActivityCommentRoot = { workspaceSlug: string; projectId: string; issueId: string; + selectedFilters: EActivityFilterType[]; activityOperations: TActivityOperations; showAccessSpecifier?: boolean; disabled?: boolean; }; export const IssueActivityCommentRoot: FC = observer((props) => { - const { workspaceSlug, issueId, activityOperations, showAccessSpecifier, projectId, disabled } = props; + const { workspaceSlug, issueId, selectedFilters, activityOperations, showAccessSpecifier, projectId, disabled } = + props; // hooks const { activity: { getActivityCommentByIssueId }, @@ -28,9 +31,19 @@ export const IssueActivityCommentRoot: FC = observer( const activityComments = getActivityCommentByIssueId(issueId); if (!activityComments || (activityComments && activityComments.length <= 0)) return <>; + + const isCommentFilterSelected = selectedFilters.includes(EActivityFilterType.COMMENT); + const isActivityFilterSelected = selectedFilters.includes(EActivityFilterType.ACTIVITY); + + const filteredActivityComments = activityComments.filter( + (activityComment) => + (activityComment.activity_type === "COMMENT" && isCommentFilterSelected) || + (activityComment.activity_type === "ACTIVITY" && isActivityFilterSelected) + ); + return (
- {activityComments.map((activityComment, index) => + {filteredActivityComments.map((activityComment, index) => activityComment.activity_type === "COMMENT" ? ( = observer( workspaceSlug={workspaceSlug} commentId={activityComment.id} activityOperations={activityOperations} - ends={index === 0 ? "top" : index === activityComments.length - 1 ? "bottom" : undefined} + ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined} showAccessSpecifier={showAccessSpecifier} disabled={disabled} /> ) : activityComment.activity_type === "ACTIVITY" ? ( - ) : ( <> diff --git a/web/core/components/issues/issue-detail/issue-activity/activity-filter.tsx b/web/core/components/issues/issue-detail/issue-activity/activity-filter.tsx new file mode 100644 index 0000000000..f216d97b34 --- /dev/null +++ b/web/core/components/issues/issue-detail/issue-activity/activity-filter.tsx @@ -0,0 +1,83 @@ +import React, { FC, Fragment } from "react"; +import { observer } from "mobx-react"; +import { Check, ListFilter } from "lucide-react"; +import { Popover, Transition } from "@headlessui/react"; +// ui +import { Button } from "@plane/ui"; +// constants +import { ACTIVITY_FILTER_TYPE_OPTIONS, EActivityFilterType } from "@/constants/issue"; +// helper +import { cn } from "@/helpers/common.helper"; + +type Props = { + selectedFilters: EActivityFilterType[]; + toggleFilter: (filter: EActivityFilterType) => void; +}; + +export const ActivityFilter: FC = observer((props) => { + const { selectedFilters, toggleFilter } = props; + return ( + + {({ open }) => ( + <> + + + + + + +
+ {ACTIVITY_FILTER_TYPE_OPTIONS.map((filter) => { + const isSelected = selectedFilters.includes(filter.value); + return ( +
toggleFilter(filter.value)} + > +
+ {isSelected && } +
+
+ {filter.label} +
+
+ ); + })} +
+
+
+ + )} +
+ ); +}); diff --git a/web/core/components/issues/issue-detail/issue-activity/activity/actions/link.tsx b/web/core/components/issues/issue-detail/issue-activity/activity/actions/link.tsx index 7ef6244437..eecb77d4a7 100644 --- a/web/core/components/issues/issue-detail/issue-activity/activity/actions/link.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/activity/actions/link.tsx @@ -27,7 +27,7 @@ export const IssueLinkActivity: FC = observer((props) => { <> {activity.verb === "created" ? ( <> - added this + added = observer((props) => { +export const IssueActivityItem: FC = observer((props) => { const { activityId, ends } = props; // hooks const { diff --git a/web/core/components/issues/issue-detail/issue-activity/index.ts b/web/core/components/issues/issue-detail/issue-activity/index.ts index bb6b1405a4..fd2b709852 100644 --- a/web/core/components/issues/issue-detail/issue-activity/index.ts +++ b/web/core/components/issues/issue-detail/issue-activity/index.ts @@ -4,6 +4,7 @@ export * from "./activity-comment-root"; // activity export * from "./activity/activity-list"; +export * from "./activity-filter"; // issue comment export * from "./comments"; diff --git a/web/core/components/issues/issue-detail/issue-activity/root.tsx b/web/core/components/issues/issue-detail/issue-activity/root.tsx index b3cf2b6226..776c14ccc9 100644 --- a/web/core/components/issues/issue-detail/issue-activity/root.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/root.tsx @@ -1,14 +1,15 @@ "use client"; -import { FC, useMemo, useState } from "react"; +import { FC, Fragment, useMemo, useState } from "react"; import { observer } from "mobx-react"; -import { History, LucideIcon, MessageCircle } from "lucide-react"; // types import { TIssueComment } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; // components -import { IssueActivityCommentRoot, IssueCommentRoot, IssueCommentCreate } from "@/components/issues"; +import { ActivityFilter, IssueActivityCommentRoot, IssueCommentCreate } from "@/components/issues"; +// constants +import { EActivityFilterType } from "@/constants/issue"; // hooks import { useIssueDetail, useProject } from "@/hooks/store"; @@ -19,21 +20,6 @@ type TIssueActivity = { disabled?: boolean; }; -type TActivityTabs = "all" | "comments"; - -const activityTabs: { key: TActivityTabs; title: string; icon: LucideIcon }[] = [ - { - key: "comments", - title: "Comments", - icon: MessageCircle, - }, - { - key: "all", - title: "All activity", - icon: History, - }, -]; - export type TActivityOperations = { createComment: (data: Partial) => Promise; updateComment: (commentId: string, data: Partial) => Promise; @@ -46,7 +32,18 @@ export const IssueActivity: FC = observer((props) => { const { createComment, updateComment, removeComment } = useIssueDetail(); const { getProjectById } = useProject(); // state - const [activityTab, setActivityTab] = useState("comments"); + const [selectedFilters, setSelectedFilters] = useState([EActivityFilterType.COMMENT, EActivityFilterType.ACTIVITY]); + // toggle filter + const toggleFilter = (filter: EActivityFilterType) => { + setSelectedFilters((prevFilters) => { + if (prevFilters.includes(filter)) { + if (prevFilters.length === 1) return prevFilters; // Ensure at least one filter is applied + return prevFilters.filter((f) => f !== filter); + } else { + return [...prevFilters, filter]; + } + }); + }; const activityOperations: TActivityOperations = useMemo( () => ({ @@ -109,74 +106,36 @@ export const IssueActivity: FC = observer((props) => { if (!project) return <>; return ( -
+
{/* header */} -
Activity
+
+
Activity
+ +
{/* rendering activity */}
-
- {activityTabs.map((tab) => ( -
setActivityTab(tab.key)} - > -
- -
-
{tab.title}
-
- ))} -
-
- {activityTab === "all" ? ( -
- + + {!disabled && ( + - {!disabled && ( - - )} -
- ) : ( -
- - {!disabled && ( - - )} -
- )} + )} +
diff --git a/web/core/components/issues/issue-detail/main-content.tsx b/web/core/components/issues/issue-detail/main-content.tsx index c790486e89..c2200aabc4 100644 --- a/web/core/components/issues/issue-detail/main-content.tsx +++ b/web/core/components/issues/issue-detail/main-content.tsx @@ -58,7 +58,7 @@ export const IssueMainContent: React.FC = observer((props) => { return ( <> -
+
{issue.parent_id && ( = observer((props) => { )}
-
- -
+ -
- -
+ ); }); diff --git a/web/core/components/issues/issue-detail/root.tsx b/web/core/components/issues/issue-detail/root.tsx index f578ee0c10..f2ec225681 100644 --- a/web/core/components/issues/issue-detail/root.tsx +++ b/web/core/components/issues/issue-detail/root.tsx @@ -348,7 +348,7 @@ export const IssueDetailRoot: FC = observer((props) => { /> ) : (
-
+
= observer((props) => {
)} +
+
+ + Parent +
+ +
+
diff --git a/web/core/components/issues/peek-overview/view.tsx b/web/core/components/issues/peek-overview/view.tsx index ae94722a7c..1801017423 100644 --- a/web/core/components/issues/peek-overview/view.tsx +++ b/web/core/components/issues/peek-overview/view.tsx @@ -184,13 +184,6 @@ export const IssueView: FC = observer((props) => { setIsSubmitting={(value) => setIsSubmitting(value)} /> - - = observer((props) => { disabled={disabled || is_archived} /> +
+ +
+ = observer((props) => { setIsSubmitting={(value) => setIsSubmitting(value)} /> - +
+ +