Skip to content

Commit 7421437

Browse files
authored
fix: existing issues modal for cycle and module (#2664)
* fix: existing issues modal for cycle and module * refactor: existing issues modal code * fix: build errors
1 parent 1ed72c5 commit 7421437

File tree

10 files changed

+173
-171
lines changed

10 files changed

+173
-171
lines changed
Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,85 @@
1+
import { useState } from "react";
2+
import { observer } from "mobx-react-lite";
13
import { PlusIcon } from "lucide-react";
4+
// mobx store
5+
import { useMobxStore } from "lib/mobx/store-provider";
6+
// hooks
7+
import useToast from "hooks/use-toast";
28
// components
39
import { EmptyState } from "components/common";
10+
import { ExistingIssuesListModal } from "components/core";
11+
// ui
12+
import { Button } from "@plane/ui";
413
// assets
514
import emptyIssue from "public/empty-state/issue.svg";
6-
import { Button } from "@plane/ui";
15+
// types
16+
import { ISearchIssueResponse } from "types";
717

818
type Props = {
9-
openIssuesListModal: () => void;
19+
workspaceSlug: string | undefined;
20+
projectId: string | undefined;
21+
cycleId: string | undefined;
1022
};
1123

12-
export const CycleEmptyState: React.FC<Props> = ({ openIssuesListModal }) => (
13-
<div className="h-full w-full grid place-items-center">
14-
<EmptyState
15-
title="Cycle issues will appear here"
16-
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
17-
image={emptyIssue}
18-
primaryButton={{
19-
text: "New issue",
20-
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
21-
onClick: () => {
22-
const e = new KeyboardEvent("keydown", {
23-
key: "c",
24-
});
25-
document.dispatchEvent(e);
26-
},
27-
}}
28-
secondaryButton={
29-
<Button
30-
variant="neutral-primary"
31-
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} onClick={openIssuesListModal} />}
32-
>
33-
Add an existing issue
34-
</Button>
35-
}
36-
/>
37-
</div>
38-
);
24+
export const CycleEmptyState: React.FC<Props> = observer((props) => {
25+
const { workspaceSlug, projectId, cycleId } = props;
26+
// states
27+
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
28+
29+
const { cycleIssue: cycleIssueStore } = useMobxStore();
30+
31+
const { setToastAlert } = useToast();
32+
33+
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
34+
if (!workspaceSlug || !projectId || !cycleId) return;
35+
36+
const issueIds = data.map((i) => i.id);
37+
38+
await cycleIssueStore
39+
.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds)
40+
.catch(() => {
41+
setToastAlert({
42+
type: "error",
43+
title: "Error!",
44+
message: "Selected issues could not be added to the cycle. Please try again.",
45+
});
46+
});
47+
};
48+
49+
return (
50+
<>
51+
<ExistingIssuesListModal
52+
isOpen={cycleIssuesListModal}
53+
handleClose={() => setCycleIssuesListModal(false)}
54+
searchParams={{ cycle: true }}
55+
handleOnSubmit={handleAddIssuesToCycle}
56+
/>
57+
<div className="h-full w-full grid place-items-center">
58+
<EmptyState
59+
title="Cycle issues will appear here"
60+
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
61+
image={emptyIssue}
62+
primaryButton={{
63+
text: "New issue",
64+
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
65+
onClick: () => {
66+
const e = new KeyboardEvent("keydown", {
67+
key: "c",
68+
});
69+
document.dispatchEvent(e);
70+
},
71+
}}
72+
secondaryButton={
73+
<Button
74+
variant="neutral-primary"
75+
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} />}
76+
onClick={() => setCycleIssuesListModal(true)}
77+
>
78+
Add an existing issue
79+
</Button>
80+
}
81+
/>
82+
</div>
83+
</>
84+
);
85+
});

web/components/issues/issue-layouts/empty-states/module.tsx

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,78 @@ import { EmptyState } from "components/common";
44
import { Button } from "@plane/ui";
55
// assets
66
import emptyIssue from "public/empty-state/issue.svg";
7+
import { ExistingIssuesListModal } from "components/core";
8+
import { observer } from "mobx-react-lite";
9+
import { useMobxStore } from "lib/mobx/store-provider";
10+
import { ISearchIssueResponse } from "types";
11+
import useToast from "hooks/use-toast";
12+
import { useState } from "react";
713

814
type Props = {
9-
openIssuesListModal: () => void;
15+
workspaceSlug: string | undefined;
16+
projectId: string | undefined;
17+
moduleId: string | undefined;
1018
};
1119

12-
export const ModuleEmptyState: React.FC<Props> = ({ openIssuesListModal }) => (
13-
<div className="h-full w-full grid place-items-center">
14-
<EmptyState
15-
title="Module issues will appear here"
16-
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
17-
image={emptyIssue}
18-
primaryButton={{
19-
text: "New issue",
20-
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
21-
onClick: () => {
22-
const e = new KeyboardEvent("keydown", {
23-
key: "c",
24-
});
25-
document.dispatchEvent(e);
26-
},
27-
}}
28-
secondaryButton={
29-
<Button
30-
variant="neutral-primary"
31-
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} />}
32-
onClick={openIssuesListModal}
33-
>
34-
Add an existing issue
35-
</Button>
36-
}
37-
/>
38-
</div>
39-
);
20+
export const ModuleEmptyState: React.FC<Props> = observer((props) => {
21+
const { workspaceSlug, projectId, moduleId } = props;
22+
// states
23+
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
24+
25+
const { moduleIssue: moduleIssueStore } = useMobxStore();
26+
27+
const { setToastAlert } = useToast();
28+
29+
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
30+
if (!workspaceSlug || !projectId || !moduleId) return;
31+
32+
const issueIds = data.map((i) => i.id);
33+
34+
await moduleIssueStore
35+
.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds)
36+
.catch(() =>
37+
setToastAlert({
38+
type: "error",
39+
title: "Error!",
40+
message: "Selected issues could not be added to the module. Please try again.",
41+
})
42+
);
43+
};
44+
45+
return (
46+
<>
47+
<ExistingIssuesListModal
48+
isOpen={moduleIssuesListModal}
49+
handleClose={() => setModuleIssuesListModal(false)}
50+
searchParams={{ module: true }}
51+
handleOnSubmit={handleAddIssuesToModule}
52+
/>
53+
<div className="h-full w-full grid place-items-center">
54+
<EmptyState
55+
title="Module issues will appear here"
56+
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
57+
image={emptyIssue}
58+
primaryButton={{
59+
text: "New issue",
60+
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
61+
onClick: () => {
62+
const e = new KeyboardEvent("keydown", {
63+
key: "c",
64+
});
65+
document.dispatchEvent(e);
66+
},
67+
}}
68+
secondaryButton={
69+
<Button
70+
variant="neutral-primary"
71+
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} />}
72+
onClick={() => setModuleIssuesListModal(true)}
73+
>
74+
Add an existing issue
75+
</Button>
76+
}
77+
/>
78+
</div>
79+
</>
80+
);
81+
});

web/components/issues/issue-layouts/roots/cycle-layout-root.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ import { Spinner } from "@plane/ui";
2020
// helpers
2121
import { getDateRangeStatus } from "helpers/date-time.helper";
2222

23-
type Props = {
24-
openIssuesListModal: () => void;
25-
};
26-
27-
export const CycleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal }) => {
23+
export const CycleLayoutRoot: React.FC = observer(() => {
2824
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
2925

3026
const router = useRouter();
@@ -73,7 +69,11 @@ export const CycleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal
7369
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
7470
<CycleAppliedFiltersRoot />
7571
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
76-
<CycleEmptyState openIssuesListModal={openIssuesListModal} />
72+
<CycleEmptyState
73+
workspaceSlug={workspaceSlug?.toString()}
74+
projectId={projectId?.toString()}
75+
cycleId={cycleId?.toString()}
76+
/>
7777
) : (
7878
<div className="w-full h-full overflow-auto">
7979
{activeLayout === "list" ? (

web/components/issues/issue-layouts/roots/module-layout-root.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ import {
1818
// ui
1919
import { Spinner } from "@plane/ui";
2020

21-
type Props = {
22-
openIssuesListModal: () => void;
23-
};
24-
25-
export const ModuleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal }) => {
21+
export const ModuleLayoutRoot: React.FC = observer(() => {
2622
const router = useRouter();
2723
const { workspaceSlug, projectId, moduleId } = router.query as {
2824
workspaceSlug: string;
@@ -66,7 +62,11 @@ export const ModuleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal
6662
<div className="relative w-full h-full flex flex-col overflow-hidden">
6763
<ModuleAppliedFiltersRoot />
6864
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
69-
<ModuleEmptyState openIssuesListModal={openIssuesListModal} />
65+
<ModuleEmptyState
66+
workspaceSlug={workspaceSlug?.toString()}
67+
projectId={projectId?.toString()}
68+
moduleId={moduleId?.toString()}
69+
/>
7070
) : (
7171
<div className="h-full w-full overflow-auto">
7272
{activeLayout === "list" ? (

web/components/issues/issue-peek-overview/properties.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
7474
};
7575
const addIssueToCycle = async (cycleId: string) => {
7676
if (!workspaceSlug || !issue || !cycleId) return;
77-
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), issue.project_detail.id, cycleId, issue.id);
77+
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), issue.project_detail.id, cycleId, [issue.id]);
7878
};
7979

8080
const addIssueToModule = async (moduleId: string) => {
8181
if (!workspaceSlug || !issue || !moduleId) return;
8282

83-
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), issue.project_detail.id, moduleId, issue.id);
83+
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), issue.project_detail.id, moduleId, [issue.id]);
8484
};
8585
const handleLabels = (formData: Partial<IIssue>) => {
8686
issueUpdate({ ...issue, ...formData });

web/components/issues/modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
171171
const addIssueToCycle = async (issueId: string, cycleId: string) => {
172172
if (!workspaceSlug || !activeProject) return;
173173

174-
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, issueId);
174+
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, [issueId]);
175175
};
176176

177177
const addIssueToModule = async (issueId: string, moduleId: string) => {
178178
if (!workspaceSlug || !activeProject) return;
179179

180-
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, issueId);
180+
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, [issueId]);
181181
};
182182

183183
const createIssue = async (payload: Partial<IIssue>) => {

web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,29 @@
1-
import { useState, ReactElement } from "react";
1+
import { ReactElement } from "react";
22
import { useRouter } from "next/router";
33
import useSWR from "swr";
44
// mobx store
55
import { useMobxStore } from "lib/mobx/store-provider";
6-
// services
7-
import { IssueService } from "services/issue";
86
// hooks
97
import useLocalStorage from "hooks/use-local-storage";
10-
import useUser from "hooks/use-user";
11-
import useToast from "hooks/use-toast";
128
// layouts
139
import { AppLayout } from "layouts/app-layout";
1410
// components
1511
import { CycleIssuesHeader } from "components/headers";
16-
import { ExistingIssuesListModal } from "components/core";
1712
import { CycleDetailsSidebar } from "components/cycles";
1813
import { CycleLayoutRoot } from "components/issues/issue-layouts";
1914
// ui
2015
import { EmptyState } from "components/common";
2116
// assets
2217
import emptyCycle from "public/empty-state/cycle.svg";
2318
// types
24-
import { ISearchIssueResponse } from "types";
2519
import { NextPageWithLayout } from "types/app";
2620

27-
const issueService = new IssueService();
28-
2921
const CycleDetailPage: NextPageWithLayout = () => {
30-
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
31-
3222
const router = useRouter();
3323
const { workspaceSlug, projectId, cycleId } = router.query;
3424

3525
const { cycle: cycleStore } = useMobxStore();
3626

37-
const { user } = useUser();
38-
39-
const { setToastAlert } = useToast();
40-
4127
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
4228
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
4329

@@ -52,38 +38,8 @@ const CycleDetailPage: NextPageWithLayout = () => {
5238
setValue(`${!isSidebarCollapsed}`);
5339
};
5440

55-
// TODO: add this function to bulk add issues to cycle
56-
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
57-
if (!workspaceSlug || !projectId) return;
58-
59-
const payload = {
60-
issues: data.map((i) => i.id),
61-
};
62-
63-
await issueService
64-
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user)
65-
.catch(() => {
66-
setToastAlert({
67-
type: "error",
68-
title: "Error!",
69-
message: "Selected issues could not be added to the cycle. Please try again.",
70-
});
71-
});
72-
};
73-
74-
const openIssuesListModal = () => {
75-
setCycleIssuesListModal(true);
76-
};
77-
7841
return (
7942
<>
80-
{/* TODO: Update logic to bulk add issues to a cycle */}
81-
<ExistingIssuesListModal
82-
isOpen={cycleIssuesListModal}
83-
handleClose={() => setCycleIssuesListModal(false)}
84-
searchParams={{ cycle: true }}
85-
handleOnSubmit={handleAddIssuesToCycle}
86-
/>
8743
{error ? (
8844
<EmptyState
8945
image={emptyCycle}
@@ -98,7 +54,7 @@ const CycleDetailPage: NextPageWithLayout = () => {
9854
<>
9955
<div className="flex h-full w-full">
10056
<div className="h-full w-full overflow-hidden">
101-
<CycleLayoutRoot openIssuesListModal={openIssuesListModal} />
57+
<CycleLayoutRoot />
10258
</div>
10359
{cycleId && !isSidebarCollapsed && (
10460
<div

0 commit comments

Comments
 (0)