Skip to content

Commit 2f45bfb

Browse files
[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
1 parent 4b59998 commit 2f45bfb

File tree

11 files changed

+364
-249
lines changed

11 files changed

+364
-249
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";
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from "./modal";
2-
export * from "./use-end-cycle";

apps/web/ce/components/cycles/end-cycle/use-end-cycle.tsx

Lines changed: 0 additions & 7 deletions
This file was deleted.

apps/web/ce/components/views/helper.tsx

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import { ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
2-
import { useTranslation } from "@plane/i18n";
31
import type { EIssueLayoutTypes, IProjectView } from "@plane/types";
4-
import type { TContextMenuItem } from "@plane/ui";
52
import type { TWorkspaceLayoutProps } from "@/components/views/helper";
63

74
export type TLayoutSelectionProps = {
@@ -18,68 +15,6 @@ export function WorkspaceAdditionalLayouts(props: TWorkspaceLayoutProps) {
1815
return <></>;
1916
}
2017

21-
export type TMenuItemsFactoryProps = {
22-
isOwner: boolean;
23-
isAdmin: boolean;
24-
setDeleteViewModal: (open: boolean) => void;
25-
setCreateUpdateViewModal: (open: boolean) => void;
26-
handleOpenInNewTab: () => void;
27-
handleCopyText: () => void;
28-
isLocked: boolean;
29-
workspaceSlug: string;
30-
projectId?: string;
31-
viewId: string;
32-
};
33-
34-
export const useMenuItemsFactory = (props: TMenuItemsFactoryProps) => {
35-
const { isOwner, isAdmin, setDeleteViewModal, setCreateUpdateViewModal, handleOpenInNewTab, handleCopyText } = props;
36-
37-
const { t } = useTranslation();
38-
39-
const editMenuItem = () => ({
40-
key: "edit",
41-
action: () => setCreateUpdateViewModal(true),
42-
title: t("edit"),
43-
icon: Pencil,
44-
shouldRender: isOwner,
45-
});
46-
47-
const openInNewTabMenuItem = () => ({
48-
key: "open-new-tab",
49-
action: handleOpenInNewTab,
50-
title: t("open_in_new_tab"),
51-
icon: ExternalLink,
52-
});
53-
54-
const copyLinkMenuItem = () => ({
55-
key: "copy-link",
56-
action: handleCopyText,
57-
title: t("copy_link"),
58-
icon: Link,
59-
});
60-
61-
const deleteMenuItem = () => ({
62-
key: "delete",
63-
action: () => setDeleteViewModal(true),
64-
title: t("delete"),
65-
icon: Trash2,
66-
shouldRender: isOwner || isAdmin,
67-
});
68-
69-
return {
70-
editMenuItem,
71-
openInNewTabMenuItem,
72-
copyLinkMenuItem,
73-
deleteMenuItem,
74-
};
75-
};
76-
77-
export const useViewMenuItems = (props: TMenuItemsFactoryProps): TContextMenuItem[] => {
78-
const factory = useMenuItemsFactory(props);
79-
80-
return [factory.editMenuItem(), factory.openInNewTabMenuItem(), factory.copyLinkMenuItem(), factory.deleteMenuItem()];
81-
};
82-
8318
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8419
export function AdditionalHeaderItems(view: IProjectView) {
8520
return <></>;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Pencil, ExternalLink, Link, Trash2, ArchiveRestoreIcon } from "lucide-react";
2+
import { useTranslation } from "@plane/i18n";
3+
import { ArchiveIcon } from "@plane/propel/icons";
4+
import type { TContextMenuItem } from "@plane/ui";
5+
6+
/**
7+
* Unified factory for creating menu items across all entities (cycles, modules, views, epics)
8+
*/
9+
export const useQuickActionsFactory = () => {
10+
const { t } = useTranslation();
11+
12+
return {
13+
// Common menu items
14+
createEditMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
15+
key: "edit",
16+
title: t("edit"),
17+
icon: Pencil,
18+
action: handler,
19+
shouldRender,
20+
}),
21+
22+
createOpenInNewTabMenuItem: (handler: () => void): TContextMenuItem => ({
23+
key: "open-new-tab",
24+
title: t("open_in_new_tab"),
25+
icon: ExternalLink,
26+
action: handler,
27+
}),
28+
29+
createCopyLinkMenuItem: (handler: () => void): TContextMenuItem => ({
30+
key: "copy-link",
31+
title: t("copy_link"),
32+
icon: Link,
33+
action: handler,
34+
}),
35+
36+
createArchiveMenuItem: (
37+
handler: () => void,
38+
opts: { shouldRender?: boolean; disabled?: boolean; description?: string }
39+
): TContextMenuItem => ({
40+
key: "archive",
41+
title: t("archive"),
42+
icon: ArchiveIcon,
43+
action: handler,
44+
className: "items-start",
45+
iconClassName: "mt-1",
46+
description: opts.description,
47+
disabled: opts.disabled,
48+
shouldRender: opts.shouldRender,
49+
}),
50+
51+
createRestoreMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
52+
key: "restore",
53+
title: t("restore"),
54+
icon: ArchiveRestoreIcon,
55+
action: handler,
56+
shouldRender,
57+
}),
58+
59+
createDeleteMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({
60+
key: "delete",
61+
title: t("delete"),
62+
icon: Trash2,
63+
action: handler,
64+
shouldRender,
65+
}),
66+
67+
// Layout-level actions (for work item list views)
68+
createOpenInNewTab: (handler: () => void): TContextMenuItem => ({
69+
key: "open-in-new-tab",
70+
title: "Open in new tab",
71+
icon: ExternalLink,
72+
action: handler,
73+
}),
74+
75+
createCopyLayoutLinkMenuItem: (handler: () => void): TContextMenuItem => ({
76+
key: "copy-link",
77+
title: "Copy link",
78+
icon: Link,
79+
action: handler,
80+
}),
81+
};
82+
};
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+
interface UseCycleMenuItemsProps {
9+
cycleDetails: ICycle | undefined;
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+
interface UseModuleMenuItemsProps {
23+
moduleDetails: IModule | undefined;
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+
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+
interface UseLayoutMenuItemsProps {
49+
workspaceSlug: string;
50+
projectId: string;
51+
storeType: "PROJECT" | "EPIC";
52+
handleCopyLink: () => void;
53+
handleOpenInNewTab: () => void;
54+
}
55+
56+
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 && !isArchived),
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 { workspaceSlug, isOwner, isAdmin, projectId, 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 });

0 commit comments

Comments
 (0)