diff --git a/web/core/components/issues/attachment/attachment-list-item.tsx b/web/core/components/issues/attachment/attachment-list-item.tsx index 0e36027b1..6bdf4490d 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 f4a58c7d1..b078c19d1 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 940e88487..f2366f7d4 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 4393bec47..6a5263fd9 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 000000000..f216d97b3 --- /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 7ef624443..eecb77d4a 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 bb6b1405a..fd2b70985 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 b3cf2b622..776c14ccc 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 c790486e8..c2200aabc 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 f578ee0c1..f2ec22568 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 ae94722a7..180101742 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)} /> - +
+ +