From d46eb9c59a9c849132e638cadc94c7102b43d4cc Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 1 Nov 2023 19:10:29 +0530 Subject: [PATCH] chore: add issue option added in header group (#2592) * chore: add issue option added in list view group header * chore: add issue option added in kanban view group header --- .../issue-layouts/kanban/headers/assignee.tsx | 1 + .../kanban/headers/created_by.tsx | 1 + .../kanban/headers/group-by-card.tsx | 167 ++++++++++++++---- .../issue-layouts/kanban/headers/label.tsx | 1 + .../issue-layouts/kanban/headers/priority.tsx | 1 + .../issue-layouts/kanban/headers/project.tsx | 1 + .../kanban/headers/state-group.tsx | 1 + .../issue-layouts/kanban/headers/state.tsx | 1 + .../issue-layouts/list/headers/assignee.tsx | 7 +- .../issue-layouts/list/headers/created-by.tsx | 1 + .../list/headers/empty-group.tsx | 2 +- .../list/headers/group-by-card.tsx | 144 ++++++++++++--- .../issue-layouts/list/headers/label.tsx | 1 + .../issue-layouts/list/headers/priority.tsx | 1 + .../issue-layouts/list/headers/project.tsx | 7 +- .../list/headers/state-group.tsx | 1 + .../issue-layouts/list/headers/state.tsx | 1 + 17 files changed, 283 insertions(+), 56 deletions(-) diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx index 663ffa26a..231d9e44a 100644 --- a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -55,6 +55,7 @@ export const AssigneesHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{ assignees: [assignee?.member?.id] }} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx index aad256c10..660fb7bd6 100644 --- a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx @@ -52,6 +52,7 @@ export const CreatedByHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{ created_by: createdBy?.member?.id }} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index 73f125b88..bb5060326 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -1,8 +1,22 @@ import React, { FC } from "react"; +import { useRouter } from "next/router"; + +// services +import { ModuleService } from "services/module.service"; +import { IssueService } from "services/issue"; +// components +import { CustomMenu } from "@plane/ui"; +import { CreateUpdateIssueModal } from "components/issues/modal"; +import { ExistingIssuesListModal } from "components/core"; // lucide icons -import { Minimize2, Maximize2, Circle } from "lucide-react"; +import { Minimize2, Maximize2, Circle, Plus } from "lucide-react"; +// hooks +import useUser from "hooks/use-user"; +import useToast from "hooks/use-toast"; // mobx import { observer } from "mobx-react-lite"; +// types +import { IIssue, ISearchIssueResponse } from "types"; interface IHeaderGroupByCard { sub_group_by: string | null; @@ -13,43 +27,134 @@ interface IHeaderGroupByCard { count: number; kanBanToggle: any; handleKanBanToggle: any; + issuePayload: Partial; } +const moduleService = new ModuleService(); +const issueService = new IssueService(); + export const HeaderGroupByCard: FC = observer((props) => { - const { sub_group_by, column_id, icon, title, count, kanBanToggle, handleKanBanToggle } = props; + const { sub_group_by, column_id, icon, title, count, kanBanToggle, handleKanBanToggle, issuePayload } = props; const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id); + const [isOpen, setIsOpen] = React.useState(false); + const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId, moduleId, cycleId } = router.query; + + const { user } = useUser(); + + const { setToastAlert } = useToast(); + + const renderExistingIssueModal = moduleId || cycleId; + const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true }; + + const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; + + const payload = { + issues: data.map((i) => i.id), + }; + + await moduleService + .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the module. Please try again.", + }) + ); + }; + + const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; + + const payload = { + issues: data.map((i) => i.id), + }; + + await issueService + .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the cycle. Please try again.", + }); + }); + }; + return ( -
-
- {icon ? icon : } -
- -
-
- {title} -
-
- {count || 0} -
-
- - {sub_group_by === null && ( -
handleKanBanToggle("groupByHeaderMinMax", column_id)} - > - {verticalAlignPosition ? : } -
+ <> + setIsOpen(false)} prePopulateData={issuePayload} /> + {renderExistingIssueModal && ( + setOpenExistingIssueListModal(false)} + searchParams={ExistingIssuesListModalPayload} + handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle} + /> )} +
+
+ {icon ? icon : } +
- {/*
- -
*/} -
+
+
+ {title} +
+
+ {count || 0} +
+
+ + {sub_group_by === null && ( +
handleKanBanToggle("groupByHeaderMinMax", column_id)} + > + {verticalAlignPosition ? ( + + ) : ( + + )} +
+ )} + + {renderExistingIssueModal ? ( + + + + } + > + setIsOpen(true)}> + Create issue + + setOpenExistingIssueListModal(true)}> + Add an existing issue + + + ) : ( +
setIsOpen(true)} + > + +
+ )} +
+ ); }); diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx index c86852eba..1b6fb864c 100644 --- a/web/components/issues/issue-layouts/kanban/headers/label.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -55,6 +55,7 @@ export const LabelHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{ labels: [label?.id] }} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/priority.tsx b/web/components/issues/issue-layouts/kanban/headers/priority.tsx index 34f46e86b..57fe6cd07 100644 --- a/web/components/issues/issue-layouts/kanban/headers/priority.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/priority.tsx @@ -78,6 +78,7 @@ export const PriorityHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{ priority: priority?.key }} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/project.tsx b/web/components/issues/issue-layouts/kanban/headers/project.tsx index 68412769c..5e4a05207 100644 --- a/web/components/issues/issue-layouts/kanban/headers/project.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/project.tsx @@ -55,6 +55,7 @@ export const ProjectHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{ project: project?.id }} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx index 3733a0c71..33c70bddd 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx @@ -58,6 +58,7 @@ export const StateGroupHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{}} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/state.tsx b/web/components/issues/issue-layouts/kanban/headers/state.tsx index 913049c01..18a064214 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state.tsx @@ -52,6 +52,7 @@ export const StateHeader: FC = observer((props) => { count={issues_count} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} + issuePayload={{ state: state?.id }} /> ))} diff --git a/web/components/issues/issue-layouts/list/headers/assignee.tsx b/web/components/issues/issue-layouts/list/headers/assignee.tsx index 1225f96a9..edc74d9cc 100644 --- a/web/components/issues/issue-layouts/list/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/list/headers/assignee.tsx @@ -21,7 +21,12 @@ export const AssigneesHeader: FC = observer((props) => { return ( <> {assignee && ( - } title={assignee?.display_name || ""} count={issues_count} /> + } + title={assignee?.display_name || ""} + count={issues_count} + issuePayload={{ assignees: [assignee?.member?.id] }} + /> )} ); diff --git a/web/components/issues/issue-layouts/list/headers/created-by.tsx b/web/components/issues/issue-layouts/list/headers/created-by.tsx index f7f7a4c4b..0814fdbe5 100644 --- a/web/components/issues/issue-layouts/list/headers/created-by.tsx +++ b/web/components/issues/issue-layouts/list/headers/created-by.tsx @@ -22,6 +22,7 @@ export const CreatedByHeader: FC = observer((props) => { icon={} title={createdBy?.display_name || ""} count={issues_count} + issuePayload={{ created_by: createdBy?.member?.id }} /> )} diff --git a/web/components/issues/issue-layouts/list/headers/empty-group.tsx b/web/components/issues/issue-layouts/list/headers/empty-group.tsx index 2449a3fd8..59840cde6 100644 --- a/web/components/issues/issue-layouts/list/headers/empty-group.tsx +++ b/web/components/issues/issue-layouts/list/headers/empty-group.tsx @@ -11,5 +11,5 @@ export interface IEmptyHeader { export const EmptyHeader: React.FC = observer((props) => { const { column_id, column_value, issues_count } = props; - return ; + return ; }); diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index 77ef79769..7397ae3d7 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -1,40 +1,140 @@ import React from "react"; +import { useRouter } from "next/router"; + +// services +import { ModuleService } from "services/module.service"; +import { IssueService } from "services/issue"; // lucide icons -import { CircleDashed } from "lucide-react"; +import { CircleDashed, Plus } from "lucide-react"; +// components +import { CreateUpdateIssueModal } from "components/issues/modal"; +import { ExistingIssuesListModal } from "components/core"; +import { CustomMenu } from "@plane/ui"; +// hooks +import useUser from "hooks/use-user"; +import useToast from "hooks/use-toast"; // mobx import { observer } from "mobx-react-lite"; +// types +import { IIssue, ISearchIssueResponse } from "types"; interface IHeaderGroupByCard { icon?: React.ReactNode; title: string; count: number; + issuePayload: Partial; } -export const HeaderGroupByCard = observer(({ icon, title, count }: IHeaderGroupByCard) => { +const moduleService = new ModuleService(); +const issueService = new IssueService(); + +export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }: IHeaderGroupByCard) => { + const router = useRouter(); + const { workspaceSlug, projectId, moduleId, cycleId } = router.query; + const [isOpen, setIsOpen] = React.useState(false); + const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false); + + const { user } = useUser(); + + const { setToastAlert } = useToast(); + const verticalAlignPosition = false; + const renderExistingIssueModal = moduleId || cycleId; + const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true }; + + const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; + + const payload = { + issues: data.map((i) => i.id), + }; + + await moduleService + .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the module. Please try again.", + }) + ); + }; + + const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; + + const payload = { + issues: data.map((i) => i.id), + }; + + await issueService + .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Selected issues could not be added to the cycle. Please try again.", + }); + }); + }; + return ( -
-
- {icon ? icon : } -
- -
-
- {title} + <> + setIsOpen(false)} prePopulateData={issuePayload} /> + {renderExistingIssueModal && ( + setOpenExistingIssueListModal(false)} + searchParams={ExistingIssuesListModalPayload} + handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle} + /> + )} +
+
+ {icon ? icon : }
-
- {count || 0} -
-
- {/*
- -
*/} -
+
+
+ {title} +
+
+ {count || 0} +
+
+ + {renderExistingIssueModal ? ( + + + + } + > + setIsOpen(true)}> + Create issue + + setOpenExistingIssueListModal(true)}> + Add an existing issue + + + ) : ( +
setIsOpen(true)} + > + +
+ )} +
+ ); }); diff --git a/web/components/issues/issue-layouts/list/headers/label.tsx b/web/components/issues/issue-layouts/list/headers/label.tsx index f200bab57..2abe186f3 100644 --- a/web/components/issues/issue-layouts/list/headers/label.tsx +++ b/web/components/issues/issue-layouts/list/headers/label.tsx @@ -25,6 +25,7 @@ export const LabelHeader: FC = observer((props) => { icon={} title={column_value?.name || ""} count={issues_count} + issuePayload={{ labels: [label.id] }} /> )} diff --git a/web/components/issues/issue-layouts/list/headers/priority.tsx b/web/components/issues/issue-layouts/list/headers/priority.tsx index 340cee82c..72f71b13f 100644 --- a/web/components/issues/issue-layouts/list/headers/priority.tsx +++ b/web/components/issues/issue-layouts/list/headers/priority.tsx @@ -48,6 +48,7 @@ export const PriorityHeader: FC = observer((props) => { icon={} title={priority?.title || ""} count={issues_count} + issuePayload={{ priority: priority?.key }} /> )} diff --git a/web/components/issues/issue-layouts/list/headers/project.tsx b/web/components/issues/issue-layouts/list/headers/project.tsx index cb83068ab..880892af2 100644 --- a/web/components/issues/issue-layouts/list/headers/project.tsx +++ b/web/components/issues/issue-layouts/list/headers/project.tsx @@ -21,7 +21,12 @@ export const ProjectHeader: FC = observer((props) => { return ( <> {project && ( - } title={project?.name || ""} count={issues_count} /> + } + title={project?.name || ""} + count={issues_count} + issuePayload={{ project: project?.id ?? "" }} + /> )} ); diff --git a/web/components/issues/issue-layouts/list/headers/state-group.tsx b/web/components/issues/issue-layouts/list/headers/state-group.tsx index e97def5d8..9f6610e5d 100644 --- a/web/components/issues/issue-layouts/list/headers/state-group.tsx +++ b/web/components/issues/issue-layouts/list/headers/state-group.tsx @@ -29,6 +29,7 @@ export const StateGroupHeader: FC = observer((props) => { icon={} title={stateGroup?.key || ""} count={issues_count} + issuePayload={{}} /> )} diff --git a/web/components/issues/issue-layouts/list/headers/state.tsx b/web/components/issues/issue-layouts/list/headers/state.tsx index 419bfb367..e169d0fbb 100644 --- a/web/components/issues/issue-layouts/list/headers/state.tsx +++ b/web/components/issues/issue-layouts/list/headers/state.tsx @@ -22,6 +22,7 @@ export const StateHeader: FC = observer((props) => { icon={} title={state?.name || ""} count={issues_count} + issuePayload={{ state: state?.id }} /> )}