From 251f4825f4af74732ba54a627f8341d8912f6321 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 15 Jul 2025 17:54:46 +0530 Subject: [PATCH 1/2] chore: work item header quick action enhancements --- .../issue-detail-quick-actions.tsx | 126 +------ .../quick-action-dropdowns/helper.tsx | 15 + .../quick-action-dropdowns/index.ts | 1 + .../quick-action-dropdowns/issue-detail.tsx | 353 ++++++++++++++++++ .../issues/peek-overview/header.tsx | 134 ++++--- .../components/issues/peek-overview/view.tsx | 53 +-- 6 files changed, 488 insertions(+), 194 deletions(-) create mode 100644 apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx diff --git a/apps/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx b/apps/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx index 5805fe41e4b..1884d521b05 100644 --- a/apps/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx +++ b/apps/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx @@ -1,26 +1,22 @@ "use client"; -import React, { FC, useState } from "react"; +import React, { FC, useRef } from "react"; import { observer } from "mobx-react"; -import { ArchiveIcon, ArchiveRestoreIcon, LinkIcon, Trash2 } from "lucide-react"; -import { - ARCHIVABLE_STATE_GROUPS, - EUserPermissions, - EUserPermissionsLevel, - WORK_ITEM_TRACKER_EVENTS, -} from "@plane/constants"; +import { LinkIcon } from "lucide-react"; +import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { EIssuesStoreType } from "@plane/types"; import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; -import { cn, generateWorkItemLink, copyTextToClipboard } from "@plane/utils"; +import { generateWorkItemLink, copyTextToClipboard } from "@plane/utils"; // components -import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/components/issues"; +import { IssueSubscription } from "@/components/issues"; // helpers // hooks import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; -import { useIssueDetail, useIssues, useProject, useProjectState, useUser, useUserPermissions } from "@/hooks/store"; +import { useIssueDetail, useIssues, useProject, useUser } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; +import { WorkItemDetailQuickActions } from "../issue-layouts/quick-action-dropdowns"; type Props = { workspaceSlug: string; @@ -31,19 +27,16 @@ type Props = { export const IssueDetailQuickActions: FC = observer((props) => { const { workspaceSlug, projectId, issueId } = props; const { t } = useTranslation(); - // states - const [deleteIssueModal, setDeleteIssueModal] = useState(false); - const [archiveIssueModal, setArchiveIssueModal] = useState(false); - const [isRestoring, setIsRestoring] = useState(false); + + // ref + const parentRef = useRef(null); // router const router = useAppRouter(); // hooks const { data: currentUser } = useUser(); - const { allowPermissions } = useUserPermissions(); const { isMobile } = usePlatformOS(); - const { getStateById } = useProjectState(); const { getProjectIdentifierById } = useProject(); const { issue: { getIssueById }, @@ -61,7 +54,6 @@ export const IssueDetailQuickActions: FC = observer((props) => { const issue = getIssueById(issueId); if (!issue) return <>; - const stateDetails = getStateById(issue.state_id); const projectIdentifier = getProjectIdentifierById(projectId); const workItemLink = generateWorkItemLink({ @@ -133,8 +125,6 @@ export const IssueDetailQuickActions: FC = observer((props) => { const handleRestore = async () => { if (!workspaceSlug || !projectId || !issueId) return; - setIsRestoring(true); - await restoreIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString()) .then(() => { setToast({ @@ -150,40 +140,11 @@ export const IssueDetailQuickActions: FC = observer((props) => { title: t("toast.error"), message: t("issue.restore.failed.message"), }); - }) - .finally(() => setIsRestoring(false)); + }); }; - // auth - const isEditable = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.PROJECT, - workspaceSlug, - projectId - ); - const canRestoreIssue = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.PROJECT, - workspaceSlug, - projectId - ); - const isArchivingAllowed = !issue?.archived_at && isEditable; - const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group); - return ( <> - setDeleteIssueModal(false)} - isOpen={deleteIssueModal} - data={issue} - onSubmit={handleDeleteIssue} - /> - setArchiveIssueModal(false)} - data={issue} - onSubmit={handleArchiveIssue} - />
{currentUser && !issue?.archived_at && ( @@ -199,64 +160,13 @@ export const IssueDetailQuickActions: FC = observer((props) => { - {issue?.archived_at && canRestoreIssue ? ( - <> - - - - - ) : ( - <> - {isArchivingAllowed && ( - - - - )} - - )} - - {isEditable && ( - - - - )} +
diff --git a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx index da33b4b3264..b90693eadb7 100644 --- a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx +++ b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/helper.tsx @@ -274,6 +274,21 @@ export const useProjectIssueMenuItems = (props: MenuItemFactoryProps): TContextM ); }; +export const useWorkItemDetailMenuItems = (props: MenuItemFactoryProps): TContextMenuItem[] => { + const factory = useMenuItemFactory(props); + + return useMemo( + () => [ + factory.createCopyMenuItem(), + factory.createOpenInNewTabMenuItem(), + factory.createArchiveMenuItem(), + factory.createRestoreMenuItem(), + factory.createDeleteMenuItem(), + ], + [factory] + ); +}; + export const useAllIssueMenuItems = (props: MenuItemFactoryProps): TContextMenuItem[] => { const factory = useMenuItemFactory(props); diff --git a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/index.ts b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/index.ts index 075f8ae0ac8..0862c68a933 100644 --- a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/index.ts +++ b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/index.ts @@ -6,3 +6,4 @@ export * from "./module-issue"; export * from "./project-issue"; export * from "./helper"; export * from "../../workspace-draft/quick-action"; +export * from "./issue-detail"; diff --git a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx new file mode 100644 index 00000000000..9761ee6d420 --- /dev/null +++ b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx @@ -0,0 +1,353 @@ +"use client"; + +import { useState } from "react"; +import omit from "lodash/omit"; +import { observer } from "mobx-react"; +import { useParams, usePathname } from "next/navigation"; +// plane imports +import { + ARCHIVABLE_STATE_GROUPS, + EUserPermissions, + EUserPermissionsLevel, + WORK_ITEM_TRACKER_ELEMENTS, +} from "@plane/constants"; +import { EIssuesStoreType, TIssue } from "@plane/types"; +import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui"; +import { cn } from "@plane/utils"; +// components +import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; +// hooks +import { captureClick } from "@/helpers/event-tracker.helper"; +import { useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; +// plane-web components +import { DuplicateWorkItemModal } from "@/plane-web/components/issues/issue-layouts/quick-action-dropdowns"; +import { IQuickActionProps } from "../list/list-view-types"; +// helper +import { MenuItemFactoryProps, useWorkItemDetailMenuItems } from "./helper"; + +type TWorkItemDetailQuickActionProps = IQuickActionProps & { + toggleEditIssueModal?: (value: boolean) => void; + toggleDeleteIssueModal?: (value: boolean) => void; + toggleDuplicateIssueModal?: (value: boolean) => void; + toggleArchiveIssueModal?: (value: boolean) => void; + isPeekMode?: boolean; +}; + +export const WorkItemDetailQuickActions: React.FC = observer((props) => { + const { + issue, + handleDelete, + handleUpdate, + handleArchive, + handleRestore, + customActionButton, + portalElement, + readOnly = false, + placements = "bottom-end", + parentRef, + toggleEditIssueModal, + toggleDeleteIssueModal, + toggleDuplicateIssueModal, + toggleArchiveIssueModal, + isPeekMode = false, + } = props; + // router + const { workspaceSlug } = useParams(); + const pathname = usePathname(); + // states + const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); + const [issueToEdit, setIssueToEdit] = useState(undefined); + const [deleteIssueModal, setDeleteIssueModal] = useState(false); + const [archiveIssueModal, setArchiveIssueModal] = useState(false); + const [duplicateWorkItemModal, setDuplicateWorkItemModal] = useState(false); + // store hooks + const { allowPermissions } = useUserPermissions(); + const { issuesFilter } = useIssues(EIssuesStoreType.PROJECT); + const { getStateById } = useProjectState(); + const { getProjectIdentifierById } = useProject(); + // derived values + const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`; + const stateDetails = getStateById(issue.state_id); + const projectIdentifier = getProjectIdentifierById(issue?.project_id); + // auth + const isEditingAllowed = + allowPermissions( + [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + EUserPermissionsLevel.PROJECT, + workspaceSlug?.toString(), + issue.project_id ?? undefined + ) && !readOnly; + + const isArchivingAllowed = !issue.archived_at && isEditingAllowed; + const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group); + const isRestoringAllowed = !!issue.archived_at && isEditingAllowed; + + const isDeletingAllowed = isEditingAllowed; + + const isDraftIssue = pathname?.includes("draft-issues") || false; + + const duplicateIssuePayload = omit( + { + ...issue, + name: `${issue.name} (copy)`, + is_draft: isDraftIssue ? false : issue.is_draft, + sourceIssueId: issue.id, + }, + ["id"] + ); + + const customEditAction = () => { + setCreateUpdateIssueModal(true); + if (toggleEditIssueModal) toggleEditIssueModal(true); + }; + + const customDeleteAction = async () => { + setDeleteIssueModal(true); + if (toggleDeleteIssueModal) toggleDeleteIssueModal(true); + }; + + const customDuplicateAction = async () => { + setDuplicateWorkItemModal(true); + console.log("coming here 0"); + if (toggleDuplicateIssueModal) { + console.log("coming here 1"); + toggleDuplicateIssueModal(true); + } + }; + + const customArchiveAction = async () => { + setArchiveIssueModal(true); + if (toggleArchiveIssueModal) toggleArchiveIssueModal(true); + }; + + const customRestoreAction = async () => { + if (handleRestore) await handleRestore(); + }; + + // Menu items and modals using helper + const menuItemProps: MenuItemFactoryProps = { + issue, + workspaceSlug: workspaceSlug?.toString(), + projectIdentifier, + activeLayout, + isEditingAllowed, + isArchivingAllowed, + isRestoringAllowed, + isDeletingAllowed, + isInArchivableGroup, + isDraftIssue, + setIssueToEdit, + setCreateUpdateIssueModal: customEditAction, + setDeleteIssueModal: customDeleteAction, + setArchiveIssueModal: customArchiveAction, + setDuplicateWorkItemModal: customDuplicateAction, + handleDelete: customDeleteAction, + handleUpdate, + handleArchive: customArchiveAction, + handleRestore: customRestoreAction, + storeType: EIssuesStoreType.PROJECT, + }; + + // const MENU_ITEMS = useWorkItemDetailMenuItems(menuItemProps); + const baseMenuItems = useWorkItemDetailMenuItems(menuItemProps); + + const MENU_ITEMS = baseMenuItems + .map((item) => { + // Customize edit action for work item + if (item.key === "edit") { + return { + ...item, + shouldRender: isEditingAllowed && !isPeekMode, + }; + } + // Customize delete action for work item + if (item.key === "delete") { + return { + ...item, + }; + } + // Hide copy link in peek mode + if (item.key === "copy-link") { + return { + ...item, + shouldRender: !isPeekMode, + }; + } + return item; + }) + .filter((item) => item.shouldRender !== false); + + const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({ + ...item, + onClick: () => { + captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW }); + item.action(); + }, + })); + + return ( + <> + {/* Modals */} + { + setArchiveIssueModal(false); + if (toggleArchiveIssueModal) toggleArchiveIssueModal(false); + }} + onSubmit={handleArchive} + /> + { + setDeleteIssueModal(false); + if (toggleDeleteIssueModal) toggleDeleteIssueModal(false); + }} + onSubmit={handleDelete} + /> + { + setCreateUpdateIssueModal(false); + setIssueToEdit(undefined); + if (toggleEditIssueModal) toggleEditIssueModal(false); + }} + data={issueToEdit ?? duplicateIssuePayload} + onSubmit={async (data) => { + if (issueToEdit && handleUpdate) await handleUpdate(data); + }} + storeType={EIssuesStoreType.PROJECT} + isDraft={isDraftIssue} + /> + {issue.project_id && workspaceSlug && ( + { + setDuplicateWorkItemModal(false); + if (toggleDuplicateIssueModal) toggleDuplicateIssueModal(false); + }} + workspaceSlug={workspaceSlug.toString()} + projectId={issue.project_id} + /> + )} + + + + {MENU_ITEMS.map((item) => { + if (item.shouldRender === false) return null; + + // Render submenu if nestedMenuItems exist + if (item.nestedMenuItems && item.nestedMenuItems.length > 0) { + return ( + + {item.icon && } +
{item.title}
+ {item.description && ( +

+ {item.description} +

+ )} + + } + disabled={item.disabled} + className={cn( + "flex items-center gap-2", + { + "text-custom-text-400": item.disabled, + }, + item.className + )} + > + {item.nestedMenuItems.map((nestedItem) => ( + { + e.preventDefault(); + e.stopPropagation(); + captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW }); + nestedItem.action(); + }} + className={cn( + "flex items-center gap-2", + { + "text-custom-text-400": nestedItem.disabled, + }, + nestedItem.className + )} + disabled={nestedItem.disabled} + > + {nestedItem.icon && } +
+
{nestedItem.title}
+ {nestedItem.description && ( +

+ {nestedItem.description} +

+ )} +
+
+ ))} +
+ ); + } + + // Render regular menu item + return ( + { + e.preventDefault(); + e.stopPropagation(); + captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.PROJECT_VIEW }); + item.action(); + }} + className={cn( + "flex items-center gap-2", + { + "text-custom-text-400": item.disabled, + }, + item.className + )} + disabled={item.disabled} + > + {item.icon && } +
+
{item.title}
+ {item.description && ( +

+ {item.description} +

+ )} +
+
+ ); + })} +
+ + ); +}); diff --git a/apps/web/core/components/issues/peek-overview/header.tsx b/apps/web/core/components/issues/peek-overview/header.tsx index 2078d4c8a0e..1b614c872fc 100644 --- a/apps/web/core/components/issues/peek-overview/header.tsx +++ b/apps/web/core/components/issues/peek-overview/header.tsx @@ -1,15 +1,14 @@ "use client"; -import { FC } from "react"; +import { FC, useRef } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { ArchiveRestoreIcon, Link2, MoveDiagonal, MoveRight, Trash2 } from "lucide-react"; +import { Link2, MoveDiagonal, MoveRight } from "lucide-react"; // plane imports -import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants"; +import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { TNameDescriptionLoader } from "@plane/types"; +import { EIssuesStoreType, TNameDescriptionLoader } from "@plane/types"; import { - ArchiveIcon, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, @@ -18,12 +17,15 @@ import { Tooltip, setToast, } from "@plane/ui"; -import { copyUrlToClipboard, cn, generateWorkItemLink } from "@plane/utils"; +import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils"; // components -import { IssueSubscription, NameDescriptionUpdateStatus } from "@/components/issues"; +import { IssueSubscription, NameDescriptionUpdateStatus, WorkItemDetailQuickActions } from "@/components/issues"; +import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; // helpers // store hooks -import { useIssueDetail, useProject, useProjectState, useUser } from "@/hooks/store"; + +import { useIssueDetail, useIssues, useProject, useUser } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; export type TPeekModes = "side-peek" | "modal" | "full-screen"; @@ -56,9 +58,11 @@ export type PeekOverviewHeaderProps = { isArchived: boolean; disabled: boolean; embedIssue: boolean; - toggleDeleteIssueModal: (issueId: string | null) => void; - toggleArchiveIssueModal: (issueId: string | null) => void; - handleRestoreIssue: () => void; + toggleDeleteIssueModal: (value: boolean) => void; + toggleArchiveIssueModal: (value: boolean) => void; + toggleDuplicateIssueModal: (value: boolean) => void; + toggleEditIssueModal: (value: boolean) => void; + handleRestoreIssue: () => Promise; isSubmitting: TNameDescriptionLoader; }; @@ -75,23 +79,33 @@ export const IssuePeekOverviewHeader: FC = observer((pr removeRoutePeekId, toggleDeleteIssueModal, toggleArchiveIssueModal, + toggleDuplicateIssueModal, + toggleEditIssueModal, handleRestoreIssue, isSubmitting, } = props; + // ref + const parentRef = useRef(null); + // router + const router = useAppRouter(); const { t } = useTranslation(); // store hooks const { data: currentUser } = useUser(); const { issue: { getIssueById }, + setPeekIssue, + removeIssue, + archiveIssue, } = useIssueDetail(); - const { getStateById } = useProjectState(); const { isMobile } = usePlatformOS(); const { getProjectIdentifierById } = useProject(); // derived values const issueDetails = getIssueById(issueId); - const stateDetails = issueDetails ? getStateById(issueDetails?.state_id) : undefined; const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode); const projectIdentifier = getProjectIdentifierById(issueDetails?.project_id); + const { + issues: { removeIssue: removeArchivedIssue }, + } = useIssues(EIssuesStoreType.ARCHIVED); const workItemLink = generateWorkItemLink({ workspaceSlug, @@ -113,10 +127,49 @@ export const IssuePeekOverviewHeader: FC = observer((pr }); }); }; - // auth - const isArchivingAllowed = !isArchived && !disabled; - const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group); - const isRestoringAllowed = isArchived && !disabled; + + const handleDeleteIssue = async () => { + try { + const deleteIssue = issueDetails?.archived_at ? removeArchivedIssue : removeIssue; + + return deleteIssue(workspaceSlug, projectId, issueId).then(() => { + setPeekIssue(undefined); + captureSuccess({ + eventName: WORK_ITEM_TRACKER_EVENTS.delete, + payload: { id: issueId }, + }); + }); + } catch (error) { + setToast({ + title: t("toast.error "), + type: TOAST_TYPE.ERROR, + message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }), + }); + captureError({ + eventName: WORK_ITEM_TRACKER_EVENTS.delete, + payload: { id: issueId }, + error: error as Error, + }); + } + }; + + const handleArchiveIssue = async () => { + try { + await archiveIssue(workspaceSlug, projectId, issueId).then(() => { + router.push(`/${workspaceSlug}/projects/${projectId}/archives/issues/${issueDetails?.id}`); + }); + captureSuccess({ + eventName: WORK_ITEM_TRACKER_EVENTS.archive, + payload: { id: issueId }, + }); + } catch (error) { + captureError({ + eventName: WORK_ITEM_TRACKER_EVENTS.archive, + payload: { id: issueId }, + error: error as Error, + }); + } + }; return (
= observer((pr - {isArchivingAllowed && ( - - - - )} - {isRestoringAllowed && ( - - - - )} - {!disabled && ( - - - + {issueDetails && ( + )}
diff --git a/apps/web/core/components/issues/peek-overview/view.tsx b/apps/web/core/components/issues/peek-overview/view.tsx index 8e2c1e20072..12360cc6556 100644 --- a/apps/web/core/components/issues/peek-overview/view.tsx +++ b/apps/web/core/components/issues/peek-overview/view.tsx @@ -1,17 +1,16 @@ import { FC, useRef, useState } from "react"; import { observer } from "mobx-react"; +import { createPortal } from "react-dom"; // types import { EIssueServiceType, TNameDescriptionLoader } from "@plane/types"; // components import { cn } from "@plane/utils"; import { - DeleteIssueModal, IssuePeekOverviewHeader, TPeekModes, PeekOverviewIssueDetails, PeekOverviewProperties, TIssueOperations, - ArchiveIssueModal, IssuePeekOverviewLoader, IssuePeekOverviewError, IssueDetailWidgets, @@ -23,7 +22,6 @@ import useKeypress from "@/hooks/use-keypress"; import usePeekOverviewOutsideClickDetector from "@/hooks/use-peek-overview-outside-click"; // store hooks import { IssueActivity } from "../issue-detail/issue-activity"; -import { createPortal } from "react-dom"; interface IIssueView { workspaceSlug: string; @@ -54,16 +52,16 @@ export const IssueView: FC = observer((props) => { // states const [peekMode, setPeekMode] = useState("side-peek"); const [isSubmitting, setIsSubmitting] = useState("saved"); + const [isDeleteIssueModalOpen, setIsDeleteIssueModalOpen] = useState(false); + const [isArchiveIssueModalOpen, setIsArchiveIssueModalOpen] = useState(false); + const [isDuplicateIssueModalOpen, setIsDuplicateIssueModalOpen] = useState(false); + const [isEditIssueModalOpen, setIsEditIssueModalOpen] = useState(false); // ref const issuePeekOverviewRef = useRef(null); // store hooks const { setPeekIssue, isAnyModalOpen, - isDeleteIssueModalOpen, - isArchiveIssueModalOpen, - toggleDeleteIssueModal, - toggleArchiveIssueModal, issue: { getIssueById, getIsLocalDBIssueDescription }, } = useIssueDetail(); const { isAnyModalOpen: isAnyEpicModalOpen } = useIssueDetail(EIssueServiceType.EPICS); @@ -76,11 +74,19 @@ export const IssueView: FC = observer((props) => { const isLocalDBIssueDescription = getIsLocalDBIssueDescription(issueId); + const toggleDeleteIssueModal = (value: boolean) => setIsDeleteIssueModalOpen(value); + const toggleArchiveIssueModal = (value: boolean) => setIsArchiveIssueModalOpen(value); + const toggleDuplicateIssueModal = (value: boolean) => setIsDuplicateIssueModalOpen(value); + const toggleEditIssueModal = (value: boolean) => setIsEditIssueModalOpen(value); + + const isAnyLocalModalOpen = + isDeleteIssueModalOpen || isArchiveIssueModalOpen || isDuplicateIssueModalOpen || isEditIssueModalOpen; + usePeekOverviewOutsideClickDetector( issuePeekOverviewRef, () => { if (!embedIssue) { - if (!isAnyModalOpen && !isAnyEpicModalOpen) { + if (!isAnyModalOpen && !isAnyEpicModalOpen && !isAnyLocalModalOpen) { removeRoutePeekId(); } } @@ -149,6 +155,8 @@ export const IssueView: FC = observer((props) => { removeRoutePeekId={removeRoutePeekId} toggleDeleteIssueModal={toggleDeleteIssueModal} toggleArchiveIssueModal={toggleArchiveIssueModal} + toggleDuplicateIssueModal={toggleDuplicateIssueModal} + toggleEditIssueModal={toggleEditIssueModal} handleRestoreIssue={handleRestore} isArchived={is_archived} issueId={issueId} @@ -254,32 +262,5 @@ export const IssueView: FC = observer((props) => { ); - return ( - <> - {issue && !is_archived && ( - toggleArchiveIssueModal(null)} - data={issue} - onSubmit={async () => { - if (issueOperations.archive) await issueOperations.archive(workspaceSlug, projectId, issueId); - removeRoutePeekId(); - }} - /> - )} - - {issue && isDeleteIssueModalOpen === issue.id && ( - { - toggleDeleteIssueModal(null); - }} - data={issue} - onSubmit={async () => issueOperations.remove(workspaceSlug, projectId, issueId)} - /> - )} - - {shouldUsePortal && portalContainer ? createPortal(content, portalContainer) : content} - - ); + return <>{shouldUsePortal && portalContainer ? createPortal(content, portalContainer) : content}; }); From 32307b74ca600344fcbd5903301325d87553446c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 15 Jul 2025 18:19:57 +0530 Subject: [PATCH 2/2] chore: code refactor --- .../issue-layouts/quick-action-dropdowns/issue-detail.tsx | 2 -- apps/web/core/components/issues/peek-overview/header.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx index 9761ee6d420..a2cff6aa01e 100644 --- a/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx +++ b/apps/web/core/components/issues/issue-layouts/quick-action-dropdowns/issue-detail.tsx @@ -108,9 +108,7 @@ export const WorkItemDetailQuickActions: React.FC { setDuplicateWorkItemModal(true); - console.log("coming here 0"); if (toggleDuplicateIssueModal) { - console.log("coming here 1"); toggleDuplicateIssueModal(true); } }; diff --git a/apps/web/core/components/issues/peek-overview/header.tsx b/apps/web/core/components/issues/peek-overview/header.tsx index 1b614c872fc..f7f361ea0a4 100644 --- a/apps/web/core/components/issues/peek-overview/header.tsx +++ b/apps/web/core/components/issues/peek-overview/header.tsx @@ -141,7 +141,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr }); } catch (error) { setToast({ - title: t("toast.error "), + title: t("toast.error"), type: TOAST_TYPE.ERROR, message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }), });