Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions apps/web/ce/components/common/quick-actions-helper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import type { ICycle, IModule, IProjectView, IWorkspaceView } from "@plane/types";
import type { TContextMenuItem } from "@plane/ui";
import { useQuickActionsFactory } from "@/components/common/quick-actions-factory";

// Cycles
export interface UseCycleMenuItemsProps {
cycleDetails: ICycle;
isEditingAllowed: boolean;
workspaceSlug: string;
projectId: string;
cycleId: string;
handleEdit: () => void;
handleArchive: () => void;
handleRestore: () => void;
handleDelete: () => void;
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}

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";

return {
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),
],
modals: null,
};
};

// Modules
export interface UseModuleMenuItemsProps {
moduleDetails: IModule;
isEditingAllowed: boolean;
workspaceSlug: string;
projectId: string;
moduleId: string;
handleEdit: () => void;
handleArchive: () => void;
handleRestore: () => void;
handleDelete: () => void;
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}

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);

return {
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),
],
modals: null,
};
};

// Views
export interface UseViewMenuItemsProps {
isOwner: boolean;
isAdmin: boolean;
workspaceSlug: string;
projectId?: string;
view: IProjectView | IWorkspaceView;
handleEdit: () => void;
handleDelete: () => void;
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}

export const useViewMenuItems = (props: UseViewMenuItemsProps): MenuResult => {
const factory = useQuickActionsFactory();

return {
items: [
factory.createEditMenuItem(props.handleEdit, props.isOwner),
factory.createOpenInNewTabMenuItem(props.handleOpenInNewTab),
factory.createCopyLinkMenuItem(props.handleCopyLink),
factory.createDeleteMenuItem(props.handleDelete, props.isOwner || props.isAdmin),
],
modals: null,
};
};

export interface UseLayoutMenuItemsProps {
workspaceSlug: string;
projectId: string;
storeType: "PROJECT" | "EPIC";
handleCopyLink: () => void;
handleOpenInNewTab: () => void;
}

export type MenuResult = {
items: TContextMenuItem[];
modals: JSX.Element | null;
};

export const useLayoutMenuItems = (props: UseLayoutMenuItemsProps): MenuResult => {
const factory = useQuickActionsFactory();

return {
items: [
factory.createOpenInNewTab(props.handleOpenInNewTab),
factory.createCopyLayoutLinkMenuItem(props.handleCopyLink),
],
modals: null,
};
};

export const useIntakeHeaderMenuItems = (props: {
workspaceSlug: string;
projectId: string;
handleCopyLink: () => void;
}): MenuResult => {
const factory = useQuickActionsFactory();

return {
items: [factory.createCopyLinkMenuItem(props.handleCopyLink)],
modals: null,
};
};
1 change: 0 additions & 1 deletion apps/web/ce/components/cycles/end-cycle/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./modal";
export * from "./use-end-cycle";
7 changes: 0 additions & 7 deletions apps/web/ce/components/cycles/end-cycle/use-end-cycle.tsx

This file was deleted.

65 changes: 0 additions & 65 deletions apps/web/ce/components/views/helper.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -14,67 +11,5 @@ export const GlobalViewLayoutSelection = (props: TLayoutSelectionProps) => <></>

export const WorkspaceAdditionalLayouts = (props: TWorkspaceLayoutProps) => <></>;

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 const AdditionalHeaderItems = (view: IProjectView) => <></>;
121 changes: 121 additions & 0 deletions apps/web/core/components/common/quick-actions-factory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {
Pencil,
ExternalLink,
Link,
Trash2,
ArchiveRestoreIcon,
StopCircle,
Download,
Lock,
LockOpen,
} 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)
* Contains ALL menu item creators including EE-specific ones
*/
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,
}),

// EE-specific menu items (defined in core but used only in EE)
createEndCycleMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
key: "end-cycle",
title: "End Cycle",
icon: StopCircle,
action: handler,
shouldRender,
}),

createExportMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
key: "export",
title: "Export",
icon: Download,
action: handler,
shouldRender,
}),

createLockMenuItem: (
handler: () => void,
opts: { isLocked: boolean; shouldRender?: boolean }
): TContextMenuItem => ({
key: "toggle-lock",
title: opts.isLocked ? "Unlock" : "Lock",
icon: opts.isLocked ? LockOpen : Lock,
action: handler,
shouldRender: opts.shouldRender,
}),

// Layout-level actions (for issues/epics 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,
}),
};
};
Loading
Loading