[WEB-5256]chore: quick actions refactor (#8019)

* chore: quick actions refactor

* chore: lint fix

* chore: unified factory for actions

* chore: lint fix

* * chore: removed redundant files
* chore: updated imports

* chore: updated interfaces to types

* chore: updated undefined handling
This commit is contained in:
Vamsi Krishna
2025-12-09 21:12:15 +05:30
committed by GitHub
parent 4b59998e52
commit 2f45bfb7f6
11 changed files with 365 additions and 250 deletions

View File

@@ -0,0 +1 @@
export { useQuickActionsFactory } from "@/components/common/quick-actions-factory";

View File

@@ -1,2 +1 @@
export * from "./modal";
export * from "./use-end-cycle";

View File

@@ -1,7 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const useEndCycle = (isCurrentCycle: boolean) => ({
isEndCycleModalOpen: false,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setEndCycleModalOpen: (value: boolean) => {},
endCycleContextMenu: undefined,
});

View File

@@ -1,7 +1,4 @@
import { ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import type { EIssueLayoutTypes, IProjectView } from "@plane/types";
import type { TContextMenuItem } from "@plane/ui";
import type { TWorkspaceLayoutProps } from "@/components/views/helper";
export type TLayoutSelectionProps = {
@@ -18,68 +15,6 @@ export function WorkspaceAdditionalLayouts(props: TWorkspaceLayoutProps) {
return <></>;
}
export type TMenuItemsFactoryProps = {
isOwner: boolean;
isAdmin: boolean;
setDeleteViewModal: (open: boolean) => void;
setCreateUpdateViewModal: (open: boolean) => void;
handleOpenInNewTab: () => void;
handleCopyText: () => void;
isLocked: boolean;
workspaceSlug: string;
projectId?: string;
viewId: string;
};
export const useMenuItemsFactory = (props: TMenuItemsFactoryProps) => {
const { isOwner, isAdmin, setDeleteViewModal, setCreateUpdateViewModal, handleOpenInNewTab, handleCopyText } = props;
const { t } = useTranslation();
const editMenuItem = () => ({
key: "edit",
action: () => setCreateUpdateViewModal(true),
title: t("edit"),
icon: Pencil,
shouldRender: isOwner,
});
const openInNewTabMenuItem = () => ({
key: "open-new-tab",
action: handleOpenInNewTab,
title: t("open_in_new_tab"),
icon: ExternalLink,
});
const copyLinkMenuItem = () => ({
key: "copy-link",
action: handleCopyText,
title: t("copy_link"),
icon: Link,
});
const deleteMenuItem = () => ({
key: "delete",
action: () => setDeleteViewModal(true),
title: t("delete"),
icon: Trash2,
shouldRender: isOwner || isAdmin,
});
return {
editMenuItem,
openInNewTabMenuItem,
copyLinkMenuItem,
deleteMenuItem,
};
};
export const useViewMenuItems = (props: TMenuItemsFactoryProps): TContextMenuItem[] => {
const factory = useMenuItemsFactory(props);
return [factory.editMenuItem(), factory.openInNewTabMenuItem(), factory.copyLinkMenuItem(), factory.deleteMenuItem()];
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function AdditionalHeaderItems(view: IProjectView) {
return <></>;

View File

@@ -0,0 +1,82 @@
import { Pencil, ExternalLink, Link, Trash2, ArchiveRestoreIcon } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import { ArchiveIcon } from "@plane/propel/icons";
import type { TContextMenuItem } from "@plane/ui";
/**
* Unified factory for creating menu items across all entities (cycles, modules, views, epics)
*/
export const useQuickActionsFactory = () => {
const { t } = useTranslation();
return {
// Common menu items
createEditMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
key: "edit",
title: t("edit"),
icon: Pencil,
action: handler,
shouldRender,
}),
createOpenInNewTabMenuItem: (handler: () => void): TContextMenuItem => ({
key: "open-new-tab",
title: t("open_in_new_tab"),
icon: ExternalLink,
action: handler,
}),
createCopyLinkMenuItem: (handler: () => void): TContextMenuItem => ({
key: "copy-link",
title: t("copy_link"),
icon: Link,
action: handler,
}),
createArchiveMenuItem: (
handler: () => void,
opts: { shouldRender?: boolean; disabled?: boolean; description?: string }
): TContextMenuItem => ({
key: "archive",
title: t("archive"),
icon: ArchiveIcon,
action: handler,
className: "items-start",
iconClassName: "mt-1",
description: opts.description,
disabled: opts.disabled,
shouldRender: opts.shouldRender,
}),
createRestoreMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
key: "restore",
title: t("restore"),
icon: ArchiveRestoreIcon,
action: handler,
shouldRender,
}),
createDeleteMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
key: "delete",
title: t("delete"),
icon: Trash2,
action: handler,
shouldRender,
}),
// Layout-level actions (for work item list views)
createOpenInNewTab: (handler: () => void): TContextMenuItem => ({
key: "open-in-new-tab",
title: "Open in new tab",
icon: ExternalLink,
action: handler,
}),
createCopyLayoutLinkMenuItem: (handler: () => void): TContextMenuItem => ({
key: "copy-link",
title: "Copy link",
icon: Link,
action: handler,
}),
};
};

View File

@@ -0,0 +1,145 @@
// types
import type { ICycle, IModule, IProjectView, IWorkspaceView } from "@plane/types";
import type { TContextMenuItem } from "@plane/ui";
// hooks
import { useQuickActionsFactory } from "@/plane-web/components/common/quick-actions-factory";
// Types
interface UseCycleMenuItemsProps {
cycleDetails: ICycle | undefined;
isEditingAllowed: boolean;
workspaceSlug: string;
projectId: string;
cycleId: string;
handleEdit: () => void;
handleArchive: () => void;
handleRestore: () => void;
handleDelete: () => void;
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}
interface UseModuleMenuItemsProps {
moduleDetails: IModule | undefined;
isEditingAllowed: boolean;
workspaceSlug: string;
projectId: string;
moduleId: string;
handleEdit: () => void;
handleArchive: () => void;
handleRestore: () => void;
handleDelete: () => void;
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}
interface UseViewMenuItemsProps {
isOwner: boolean;
isAdmin: boolean;
workspaceSlug: string;
projectId?: string;
view: IProjectView | IWorkspaceView;
handleEdit: () => void;
handleDelete: () => void;
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}
interface UseLayoutMenuItemsProps {
workspaceSlug: string;
projectId: string;
storeType: "PROJECT" | "EPIC";
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}
type MenuResult = {
items: TContextMenuItem[];
modals: JSX.Element | null;
};
export const useCycleMenuItems = (props: UseCycleMenuItemsProps): MenuResult => {
const factory = useQuickActionsFactory();
const { cycleDetails, isEditingAllowed, ...handlers } = props;
const isArchived = !!cycleDetails?.archived_at;
const isCompleted = cycleDetails?.status?.toLowerCase() === "completed";
// Assemble final menu items - order defined here
const items = [
factory.createEditMenuItem(handlers.handleEdit, isEditingAllowed && !isCompleted && !isArchived),
factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab),
factory.createCopyLinkMenuItem(handlers.handleCopyLink),
factory.createArchiveMenuItem(handlers.handleArchive, {
shouldRender: isEditingAllowed && !isArchived,
disabled: !isCompleted,
description: isCompleted ? undefined : "Only completed cycles can be archived",
}),
factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived),
factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed && !isCompleted && !isArchived),
].filter((item) => item.shouldRender !== false);
return { items, modals: null };
};
export const useModuleMenuItems = (props: UseModuleMenuItemsProps): MenuResult => {
const factory = useQuickActionsFactory();
const { moduleDetails, isEditingAllowed, ...handlers } = props;
const isArchived = !!moduleDetails?.archived_at;
const moduleState = moduleDetails?.status?.toLocaleLowerCase();
const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState);
// Assemble final menu items - order defined here
const items = [
factory.createEditMenuItem(handlers.handleEdit, isEditingAllowed && !isArchived),
factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab),
factory.createCopyLinkMenuItem(handlers.handleCopyLink),
factory.createArchiveMenuItem(handlers.handleArchive, {
shouldRender: isEditingAllowed && !isArchived,
disabled: !isInArchivableGroup,
description: isInArchivableGroup ? undefined : "Only completed or cancelled modules can be archived",
}),
factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived),
factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed && !isArchived),
].filter((item) => item.shouldRender !== false);
return { items, modals: null };
};
export const useViewMenuItems = (props: UseViewMenuItemsProps): MenuResult => {
const factory = useQuickActionsFactory();
const { workspaceSlug, isOwner, isAdmin, projectId, view, ...handlers } = props;
if (!view) return { items: [], modals: null };
// Assemble final menu items - order defined here
const items = [
factory.createEditMenuItem(handlers.handleEdit, isOwner),
factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab),
factory.createCopyLinkMenuItem(handlers.handleCopyLink),
factory.createDeleteMenuItem(handlers.handleDelete, isOwner || isAdmin),
].filter((item) => item.shouldRender !== false);
return { items, modals: null };
};
export const useLayoutMenuItems = (props: UseLayoutMenuItemsProps): MenuResult => {
const factory = useQuickActionsFactory();
const { ...handlers } = props;
// Assemble final menu items - order defined here
const items = [
factory.createOpenInNewTab(handlers.handleOpenInNewTab),
factory.createCopyLayoutLinkMenuItem(handlers.handleCopyLink),
].filter((item) => item.shouldRender !== false);
return { items, modals: null };
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const useIntakeHeaderMenuItems = (props: {
workspaceSlug: string;
projectId: string;
handleCopyLink: () => void;
}): MenuResult => ({ items: [], modals: null });

View File

@@ -1,8 +1,6 @@
import { useState } from "react";
import { observer } from "mobx-react";
// icons
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
// ui
import {
CYCLE_TRACKER_EVENTS,
@@ -11,18 +9,17 @@ import {
CYCLE_TRACKER_ELEMENTS,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { ArchiveIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TContextMenuItem } from "@plane/ui";
import { ContextMenu, CustomMenu } from "@plane/ui";
import { copyUrlToClipboard, cn } from "@plane/utils";
// helpers
// hooks
import { useCycleMenuItems } from "@/components/common/quick-actions-helper";
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { useCycle } from "@/hooks/store/use-cycle";
import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { useEndCycle, EndCycleModal } from "@/plane-web/components/cycles";
// local imports
import { ArchiveCycleModal } from "./archived-cycles/modal";
import { CycleDeleteModal } from "./delete-modal";
@@ -50,12 +47,6 @@ export const CycleQuickActions = observer(function CycleQuickActions(props: Prop
const { t } = useTranslation();
// derived values
const cycleDetails = getCycleById(cycleId);
const isArchived = !!cycleDetails?.archived_at;
const isCompleted = cycleDetails?.status?.toLowerCase() === "completed";
const isCurrentCycle = cycleDetails?.status?.toLowerCase() === "current";
const transferrableIssuesCount = cycleDetails
? cycleDetails.total_issues - (cycleDetails.cancelled_issues + cycleDetails.completed_issues)
: 0;
// auth
const isEditingAllowed = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
@@ -64,8 +55,6 @@ export const CycleQuickActions = observer(function CycleQuickActions(props: Prop
projectId
);
const { isEndCycleModalOpen, setEndCycleModalOpen, endCycleContextMenu } = useEndCycle(isCurrentCycle);
const cycleLink = `${workspaceSlug}/projects/${projectId}/cycles/${cycleId}`;
const handleCopyText = () =>
copyUrlToClipboard(cycleLink).then(() => {
@@ -77,12 +66,6 @@ export const CycleQuickActions = observer(function CycleQuickActions(props: Prop
});
const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank");
const handleEditCycle = () => {
setUpdateModal(true);
};
const handleArchiveCycle = () => setArchiveCycleModal(true);
const handleRestoreCycle = async () =>
await restoreCycle(workspaceSlug, projectId, cycleId)
.then(() => {
@@ -113,60 +96,22 @@ export const CycleQuickActions = observer(function CycleQuickActions(props: Prop
});
});
const handleDeleteCycle = () => {
setDeleteModal(true);
};
const menuResult = useCycleMenuItems({
cycleDetails: cycleDetails ?? undefined,
workspaceSlug,
projectId,
cycleId,
isEditingAllowed,
handleEdit: () => setUpdateModal(true),
handleArchive: () => setArchiveCycleModal(true),
handleRestore: handleRestoreCycle,
handleDelete: () => setDeleteModal(true),
handleCopyLink: handleCopyText,
handleOpenInNewTab,
});
const MENU_ITEMS: TContextMenuItem[] = [
{
key: "edit",
title: t("edit"),
icon: Pencil,
action: handleEditCycle,
shouldRender: isEditingAllowed && !isCompleted && !isArchived,
},
{
key: "open-new-tab",
action: handleOpenInNewTab,
title: t("open_in_new_tab"),
icon: ExternalLink,
shouldRender: !isArchived,
},
{
key: "copy-link",
action: handleCopyText,
title: t("copy_link"),
icon: LinkIcon,
shouldRender: !isArchived,
},
{
key: "archive",
action: handleArchiveCycle,
title: t("archive"),
description: isCompleted ? undefined : t("project_cycles.only_completed_cycles_can_be_archived"),
icon: ArchiveIcon,
className: "items-start",
iconClassName: "mt-1",
shouldRender: isEditingAllowed && !isArchived,
disabled: !isCompleted,
},
{
key: "restore",
action: handleRestoreCycle,
title: t("restore"),
icon: ArchiveRestoreIcon,
shouldRender: isEditingAllowed && isArchived,
},
{
key: "delete",
action: handleDeleteCycle,
title: t("delete"),
icon: Trash2,
shouldRender: isEditingAllowed && !isCompleted && !isArchived,
},
];
if (endCycleContextMenu) MENU_ITEMS.splice(3, 0, endCycleContextMenu);
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
const CONTEXT_MENU_ITEMS = MENU_ITEMS.map(function CONTEXT_MENU_ITEMS(item) {
return {
@@ -206,17 +151,7 @@ export const CycleQuickActions = observer(function CycleQuickActions(props: Prop
workspaceSlug={workspaceSlug}
projectId={projectId}
/>
{isCurrentCycle && (
<EndCycleModal
isOpen={isEndCycleModalOpen}
handleClose={() => setEndCycleModalOpen(false)}
cycleId={cycleId}
projectId={projectId}
workspaceSlug={workspaceSlug}
transferrableIssuesCount={transferrableIssuesCount}
cycleName={cycleDetails.name}
/>
)}
{additionalModals}
</div>
)}
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />

View File

@@ -0,0 +1,72 @@
"use client";
import { observer } from "mobx-react";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TContextMenuItem } from "@plane/ui";
import { CustomMenu } from "@plane/ui";
import { copyUrlToClipboard, cn } from "@plane/utils";
import { useLayoutMenuItems } from "@/components/common/quick-actions-helper";
type Props = {
workspaceSlug: string;
projectId: string;
storeType: "PROJECT" | "EPIC";
};
export const LayoutQuickActions: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, storeType } = props;
const layoutLink = `${workspaceSlug}/projects/${projectId}/${storeType === "EPIC" ? "epics" : "issues"}`;
const handleCopyLink = () =>
copyUrlToClipboard(layoutLink).then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link copied",
message: `${storeType === "EPIC" ? "Epics" : "Work items"} link copied to clipboard.`,
});
});
const handleOpenInNewTab = () => window.open(`/${layoutLink}`, "_blank");
const menuResult = useLayoutMenuItems({
workspaceSlug,
projectId,
storeType,
handleCopyLink,
handleOpenInNewTab,
});
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
return (
<>
{additionalModals}
<CustomMenu
ellipsis
placement="bottom-end"
closeOnSelect
maxHeight="lg"
className="flex-shrink-0 flex items-center justify-center size-[26px] bg-custom-background-80/70 rounded"
>
{MENU_ITEMS.map((item) => {
if (item.shouldRender === false) return null;
return (
<CustomMenu.MenuItem
key={item.key}
onClick={item.action}
className={cn("flex items-center gap-2", {
"text-custom-text-400": item.disabled,
})}
disabled={item.disabled}
>
{item.icon && <item.icon className="h-3 w-3" />}
<span>{item.title}</span>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
</>
);
});

View File

@@ -1,8 +1,6 @@
import { useState } from "react";
import { observer } from "mobx-react";
// icons
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
// plane imports
import {
EUserPermissions,
@@ -11,13 +9,12 @@ import {
MODULE_TRACKER_EVENTS,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// ui
import { ArchiveIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TContextMenuItem } from "@plane/ui";
import { ContextMenu, CustomMenu } from "@plane/ui";
import { copyUrlToClipboard, cn } from "@plane/utils";
// components
import { useModuleMenuItems } from "@/components/common/quick-actions-helper";
import { ArchiveModuleModal, CreateUpdateModuleModal, DeleteModuleModal } from "@/components/modules";
// helpers
import { captureClick, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
@@ -50,7 +47,6 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
const { t } = useTranslation();
// derived values
const moduleDetails = getModuleById(moduleId);
const isArchived = !!moduleDetails?.archived_at;
// auth
const isEditingAllowed = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
@@ -59,9 +55,6 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
projectId
);
const moduleState = moduleDetails?.status?.toLocaleLowerCase();
const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState);
const moduleLink = `${workspaceSlug}/projects/${projectId}/modules/${moduleId}`;
const handleCopyText = () =>
copyUrlToClipboard(moduleLink).then(() => {
@@ -73,12 +66,6 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
});
const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank");
const handleEditModule = () => {
setEditModal(true);
};
const handleArchiveModule = () => setArchiveModuleModal(true);
const handleRestoreModule = async () =>
await restoreModule(workspaceSlug, projectId, moduleId)
.then(() => {
@@ -106,72 +93,35 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
});
});
const handleDeleteModule = () => {
setDeleteModal(true);
};
const MENU_ITEMS: TContextMenuItem[] = [
{
key: "edit",
title: t("edit"),
icon: Pencil,
action: handleEditModule,
shouldRender: isEditingAllowed && !isArchived,
},
{
key: "open-new-tab",
action: handleOpenInNewTab,
title: t("open_in_new_tab"),
icon: ExternalLink,
shouldRender: !isArchived,
},
{
key: "copy-link",
action: handleCopyText,
title: t("copy_link"),
icon: LinkIcon,
shouldRender: !isArchived,
},
{
key: "archive",
action: handleArchiveModule,
title: t("archive"),
description: isInArchivableGroup ? undefined : t("project_module.quick_actions.archive_module_description"),
icon: ArchiveIcon,
className: "items-start",
iconClassName: "mt-1",
shouldRender: isEditingAllowed && !isArchived,
disabled: !isInArchivableGroup,
},
{
key: "restore",
action: handleRestoreModule,
title: t("restore"),
icon: ArchiveRestoreIcon,
shouldRender: isEditingAllowed && isArchived,
},
{
key: "delete",
action: handleDeleteModule,
title: t("delete"),
icon: Trash2,
shouldRender: isEditingAllowed,
},
];
const CONTEXT_MENU_ITEMS = MENU_ITEMS.map(function CONTEXT_MENU_ITEMS(item) {
return {
...item,
onClick: () => {
captureClick({
elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU,
});
item.action();
},
};
// Use unified menu hook from plane-web (resolves to CE or EE)
const menuResult = useModuleMenuItems({
moduleDetails: moduleDetails ?? undefined,
workspaceSlug,
projectId,
moduleId,
isEditingAllowed,
handleEdit: () => setEditModal(true),
handleArchive: () => setArchiveModuleModal(true),
handleRestore: handleRestoreModule,
handleDelete: () => setDeleteModal(true),
handleCopyLink: handleCopyText,
handleOpenInNewTab,
});
// Handle both CE (array) and EE (object) return types
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
...item,
action: () => {
captureClick({
elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU,
});
item.action();
},
}));
return (
<>
{moduleDetails && (
@@ -191,6 +141,7 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
handleClose={() => setArchiveModuleModal(false)}
/>
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
{additionalModals}
</div>
)}
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />

View File

@@ -9,10 +9,10 @@ import type { TContextMenuItem } from "@plane/ui";
import { ContextMenu, CustomMenu } from "@plane/ui";
import { copyUrlToClipboard, cn } from "@plane/utils";
// helpers
import { useViewMenuItems } from "@/components/common/quick-actions-helper";
import { captureClick } from "@/helpers/event-tracker.helper";
// hooks
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { useViewMenuItems } from "@/plane-web/components/views/helper";
import { PublishViewModal, useViewPublish } from "@/plane-web/components/views/publish";
// local imports
import { DeleteProjectViewModal } from "./delete-view-modal";
@@ -54,19 +54,22 @@ export const ViewQuickActions = observer(function ViewQuickActions(props: Props)
});
const handleOpenInNewTab = () => window.open(`/${viewLink}`, "_blank");
const MENU_ITEMS: TContextMenuItem[] = useViewMenuItems({
const menuResult = useViewMenuItems({
isOwner,
isAdmin,
setDeleteViewModal,
setCreateUpdateViewModal,
handleOpenInNewTab,
handleCopyText,
isLocked: view.is_locked,
workspaceSlug,
projectId,
viewId: view.id,
view,
handleEdit: () => setCreateUpdateViewModal(true),
handleDelete: () => setDeleteViewModal(true),
handleCopyLink: handleCopyText,
handleOpenInNewTab,
});
// Handle both CE (array) and EE (object) return types
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
if (publishContextMenu) MENU_ITEMS.splice(2, 0, publishContextMenu);
const CONTEXT_MENU_ITEMS = MENU_ITEMS.map(function CONTEXT_MENU_ITEMS(item) {
@@ -91,6 +94,7 @@ export const ViewQuickActions = observer(function ViewQuickActions(props: Props)
/>
<DeleteProjectViewModal data={view} isOpen={deleteViewModal} onClose={() => setDeleteViewModal(false)} />
<PublishViewModal isOpen={isPublishModalOpen} onClose={() => setPublishModalOpen(false)} view={view} />
{additionalModals}
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
{MENU_ITEMS.map((item) => {

View File

@@ -4,14 +4,13 @@ import { observer } from "mobx-react";
import { EUserPermissions, EUserPermissionsLevel, GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IWorkspaceView } from "@plane/types";
import type { TContextMenuItem } from "@plane/ui";
import { CustomMenu } from "@plane/ui";
import { copyUrlToClipboard, cn } from "@plane/utils";
// helpers
import { useViewMenuItems } from "@/components/common/quick-actions-helper";
import { captureClick } from "@/helpers/event-tracker.helper";
// hooks
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { useViewMenuItems } from "@/plane-web/components/views/helper";
// local imports
import { DeleteGlobalViewModal } from "./delete-view-modal";
import { CreateUpdateWorkspaceViewModal } from "./modal";
@@ -44,16 +43,15 @@ export const WorkspaceViewQuickActions = observer(function WorkspaceViewQuickAct
});
const handleOpenInNewTab = () => window.open(`/${viewLink}`, "_blank");
const MENU_ITEMS: TContextMenuItem[] = useViewMenuItems({
const MENU_ITEMS = useViewMenuItems({
isOwner,
isAdmin,
setDeleteViewModal,
setCreateUpdateViewModal: setUpdateViewModal,
handleDelete: () => setDeleteViewModal(true),
handleEdit: () => setUpdateViewModal(true),
handleOpenInNewTab,
handleCopyText,
isLocked: view.is_locked,
handleCopyLink: handleCopyText,
workspaceSlug,
viewId: view.id,
view,
});
return (
@@ -66,7 +64,7 @@ export const WorkspaceViewQuickActions = observer(function WorkspaceViewQuickAct
closeOnSelect
buttonClassName="flex-shrink-0 flex items-center justify-center size-[26px] bg-custom-background-80/70 rounded"
>
{MENU_ITEMS.map((item) => {
{MENU_ITEMS.items.map((item) => {
if (item.shouldRender === false) return null;
return (
<CustomMenu.MenuItem