Skip to content

Commit 33b2a79

Browse files
chore: unified factory for actions
1 parent 4e9d942 commit 33b2a79

File tree

6 files changed

+149
-46
lines changed

6 files changed

+149
-46
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { useQuickActionsFactory } from "@/components/common/quick-actions-factory";

apps/web/ce/components/common/quick-actions-helper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const useModuleMenuItems = (props: UseModuleMenuItemsProps): MenuResult =
7575
description: isInArchivableGroup ? undefined : "Only completed or cancelled modules can be archived",
7676
}),
7777
factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived),
78-
factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed),
78+
factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed && !isArchived),
7979
],
8080
modals: null,
8181
};

apps/web/core/components/common/quick-actions-factory.tsx

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
import {
2-
Pencil,
3-
ExternalLink,
4-
Link,
5-
Trash2,
6-
ArchiveRestoreIcon,
7-
StopCircle,
8-
Download,
9-
Lock,
10-
LockOpen,
11-
} from "lucide-react";
1+
import { Pencil, ExternalLink, Link, Trash2, ArchiveRestoreIcon } from "lucide-react";
122
import { useTranslation } from "@plane/i18n";
133
import { ArchiveIcon } from "@plane/propel/icons";
144
import type { TContextMenuItem } from "@plane/ui";
155

166
/**
177
* Unified factory for creating menu items across all entities (cycles, modules, views, epics)
18-
* Contains ALL menu item creators including EE-specific ones
198
*/
209
export const useQuickActionsFactory = () => {
2110
const { t } = useTranslation();
@@ -75,35 +64,7 @@ export const useQuickActionsFactory = () => {
7564
shouldRender,
7665
}),
7766

78-
// EE-specific menu items (defined in core but used only in EE)
79-
createEndCycleMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
80-
key: "end-cycle",
81-
title: "End Cycle",
82-
icon: StopCircle,
83-
action: handler,
84-
shouldRender,
85-
}),
86-
87-
createExportMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
88-
key: "export",
89-
title: "Export",
90-
icon: Download,
91-
action: handler,
92-
shouldRender,
93-
}),
94-
95-
createLockMenuItem: (
96-
handler: () => void,
97-
opts: { isLocked: boolean; shouldRender?: boolean }
98-
): TContextMenuItem => ({
99-
key: "toggle-lock",
100-
title: opts.isLocked ? "Unlock" : "Lock",
101-
icon: opts.isLocked ? LockOpen : Lock,
102-
action: handler,
103-
shouldRender: opts.shouldRender,
104-
}),
105-
106-
// Layout-level actions (for issues/epics list views)
67+
// Layout-level actions (for work item list views)
10768
createOpenInNewTab: (handler: () => void): TContextMenuItem => ({
10869
key: "open-in-new-tab",
10970
title: "Open in new tab",
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// types
2+
import type { ICycle, IModule, IProjectView, IWorkspaceView } from "@plane/types";
3+
import type { TContextMenuItem } from "@plane/ui";
4+
// hooks
5+
import { useQuickActionsFactory } from "@/plane-web/components/common/quick-actions-factory";
6+
7+
// Types
8+
export interface UseCycleMenuItemsProps {
9+
cycleDetails: ICycle;
10+
isEditingAllowed: boolean;
11+
workspaceSlug: string;
12+
projectId: string;
13+
cycleId: string;
14+
handleEdit: () => void;
15+
handleArchive: () => void;
16+
handleRestore: () => void;
17+
handleDelete: () => void;
18+
handleCopyLink: () => void;
19+
handleOpenInNewTab: () => void;
20+
}
21+
22+
export interface UseModuleMenuItemsProps {
23+
moduleDetails: IModule;
24+
isEditingAllowed: boolean;
25+
workspaceSlug: string;
26+
projectId: string;
27+
moduleId: string;
28+
handleEdit: () => void;
29+
handleArchive: () => void;
30+
handleRestore: () => void;
31+
handleDelete: () => void;
32+
handleCopyLink: () => void;
33+
handleOpenInNewTab: () => void;
34+
}
35+
36+
export interface UseViewMenuItemsProps {
37+
isOwner: boolean;
38+
isAdmin: boolean;
39+
workspaceSlug: string;
40+
projectId?: string;
41+
view: IProjectView | IWorkspaceView;
42+
handleEdit: () => void;
43+
handleDelete: () => void;
44+
handleCopyLink: () => void;
45+
handleOpenInNewTab: () => void;
46+
}
47+
48+
export interface UseLayoutMenuItemsProps {
49+
workspaceSlug: string;
50+
projectId: string;
51+
storeType: "PROJECT" | "EPIC";
52+
handleCopyLink: () => void;
53+
handleOpenInNewTab: () => void;
54+
}
55+
56+
export type MenuResult = {
57+
items: TContextMenuItem[];
58+
modals: JSX.Element | null;
59+
};
60+
61+
export const useCycleMenuItems = (props: UseCycleMenuItemsProps): MenuResult => {
62+
const factory = useQuickActionsFactory();
63+
const { cycleDetails, isEditingAllowed, ...handlers } = props;
64+
65+
const isArchived = !!cycleDetails?.archived_at;
66+
const isCompleted = cycleDetails?.status?.toLowerCase() === "completed";
67+
68+
// Assemble final menu items - order defined here
69+
const items = [
70+
factory.createEditMenuItem(handlers.handleEdit, isEditingAllowed && !isCompleted && !isArchived),
71+
factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab),
72+
factory.createCopyLinkMenuItem(handlers.handleCopyLink),
73+
factory.createArchiveMenuItem(handlers.handleArchive, {
74+
shouldRender: isEditingAllowed && !isArchived,
75+
disabled: !isCompleted,
76+
description: isCompleted ? undefined : "Only completed cycles can be archived",
77+
}),
78+
factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived),
79+
factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed && !isCompleted && !isArchived),
80+
].filter((item) => item.shouldRender !== false);
81+
82+
return { items, modals: null };
83+
};
84+
85+
export const useModuleMenuItems = (props: UseModuleMenuItemsProps): MenuResult => {
86+
const factory = useQuickActionsFactory();
87+
const { moduleDetails, isEditingAllowed, ...handlers } = props;
88+
89+
const isArchived = !!moduleDetails?.archived_at;
90+
const moduleState = moduleDetails?.status?.toLocaleLowerCase();
91+
const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState);
92+
93+
// Assemble final menu items - order defined here
94+
const items = [
95+
factory.createEditMenuItem(handlers.handleEdit, isEditingAllowed && !isArchived),
96+
factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab),
97+
factory.createCopyLinkMenuItem(handlers.handleCopyLink),
98+
factory.createArchiveMenuItem(handlers.handleArchive, {
99+
shouldRender: isEditingAllowed && !isArchived,
100+
disabled: !isInArchivableGroup,
101+
description: isInArchivableGroup ? undefined : "Only completed or cancelled modules can be archived",
102+
}),
103+
factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived),
104+
factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed),
105+
].filter((item) => item.shouldRender !== false);
106+
107+
return { items, modals: null };
108+
};
109+
110+
export const useViewMenuItems = (props: UseViewMenuItemsProps): MenuResult => {
111+
const factory = useQuickActionsFactory();
112+
const { isOwner, isAdmin, view, ...handlers } = props;
113+
114+
if (!view) return { items: [], modals: null };
115+
116+
// Assemble final menu items - order defined here
117+
const items = [
118+
factory.createEditMenuItem(handlers.handleEdit, isOwner),
119+
factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab),
120+
factory.createCopyLinkMenuItem(handlers.handleCopyLink),
121+
factory.createDeleteMenuItem(handlers.handleDelete, isOwner || isAdmin),
122+
].filter((item) => item.shouldRender !== false);
123+
124+
return { items, modals: null };
125+
};
126+
127+
export const useLayoutMenuItems = (props: UseLayoutMenuItemsProps): MenuResult => {
128+
const factory = useQuickActionsFactory();
129+
const { ...handlers } = props;
130+
131+
// Assemble final menu items - order defined here
132+
const items = [
133+
factory.createOpenInNewTab(handlers.handleOpenInNewTab),
134+
factory.createCopyLayoutLinkMenuItem(handlers.handleCopyLink),
135+
].filter((item) => item.shouldRender !== false);
136+
137+
return { items, modals: null };
138+
};
139+
140+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
141+
export const useIntakeHeaderMenuItems = (props: {
142+
workspaceSlug: string;
143+
projectId: string;
144+
handleCopyLink: () => void;
145+
}): MenuResult => ({ items: [], modals: null });

apps/web/core/components/cycles/quick-actions.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
9898
});
9999
});
100100

101-
// Use unified menu hook from plane-web (resolves to CE or EE)
102101
const menuResult = useCycleMenuItems({
103102
cycleDetails: cycleDetails!,
104103
workspaceSlug,
@@ -113,7 +112,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
113112
handleOpenInNewTab,
114113
});
115114

116-
// Handle both CE (array) and EE (object) return types
117115
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
118116
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
119117

apps/web/core/components/issues/layout-quick-actions.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export const LayoutQuickActions: React.FC<Props> = observer((props) => {
2929

3030
const handleOpenInNewTab = () => window.open(`/${layoutLink}`, "_blank");
3131

32-
// Use unified menu hook from plane-web (resolves to CE or EE)
3332
const menuResult = useLayoutMenuItems({
3433
workspaceSlug,
3534
projectId,
@@ -38,7 +37,6 @@ export const LayoutQuickActions: React.FC<Props> = observer((props) => {
3837
handleOpenInNewTab,
3938
});
4039

41-
// Handle both CE (array) and EE (object) return types
4240
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
4341
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
4442

0 commit comments

Comments
 (0)