From 20a299e28d66862c68d1b9ded29842dc71e16ea4 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 21 Sep 2023 13:36:31 +0530 Subject: [PATCH 1/4] feat: subissues infinte level --- web/components/issues/main-content.tsx | 4 +- web/components/issues/sub-issues/index.ts | 1 + web/components/issues/sub-issues/issue.tsx | 159 ++++++++++++ .../issues/sub-issues/issues-list.tsx | 71 ++++++ .../issues/sub-issues/progressbar.tsx | 25 ++ .../issues/sub-issues/properties.tsx | 204 +++++++++++++++ web/components/issues/sub-issues/root.tsx | 239 ++++++++++++++++++ web/components/project/members-select.tsx | 2 +- 8 files changed, 703 insertions(+), 2 deletions(-) create mode 100644 web/components/issues/sub-issues/index.ts create mode 100644 web/components/issues/sub-issues/issue.tsx create mode 100644 web/components/issues/sub-issues/issues-list.tsx create mode 100644 web/components/issues/sub-issues/progressbar.tsx create mode 100644 web/components/issues/sub-issues/properties.tsx create mode 100644 web/components/issues/sub-issues/root.tsx diff --git a/web/components/issues/main-content.tsx b/web/components/issues/main-content.tsx index b7b154ce2c6..d1f59e8ef53 100644 --- a/web/components/issues/main-content.tsx +++ b/web/components/issues/main-content.tsx @@ -21,6 +21,7 @@ import { SubIssuesList, IssueReaction, } from "components/issues"; +import { SubIssuesRoot } from "./sub-issues"; // ui import { CustomMenu } from "components/ui"; // icons @@ -206,7 +207,8 @@ export const IssueMainContent: React.FC = ({
- + + {/* */}
diff --git a/web/components/issues/sub-issues/index.ts b/web/components/issues/sub-issues/index.ts new file mode 100644 index 00000000000..1efe34c51ec --- /dev/null +++ b/web/components/issues/sub-issues/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/web/components/issues/sub-issues/issue.tsx b/web/components/issues/sub-issues/issue.tsx new file mode 100644 index 00000000000..bca67fa043d --- /dev/null +++ b/web/components/issues/sub-issues/issue.tsx @@ -0,0 +1,159 @@ +import React from "react"; +// next imports +import Link from "next/link"; +// lucide icons +import { + ChevronDown, + ChevronRight, + X, + Pencil, + Trash, + Link as LinkIcon, + Loader, +} from "lucide-react"; +// components +import { SubIssuesRootList } from "./issues-list"; +import { IssueProperty } from "./properties"; +// ui +import { CustomMenu } from "components/ui"; +// types +import { ICurrentUserResponse, IIssue } from "types"; + +export interface ISubIssues { + workspaceSlug: string; + projectId: string; + parentIssue: IIssue; + issue: any; + spacingLeft?: number; + user: ICurrentUserResponse | undefined; + editable: boolean; + removeIssueFromSubIssues: (parentIssueId: string, issue: IIssue) => void; + issuesVisibility: string[]; + handleIssuesVisibility: (issueId: string) => void; + copyText: (text: string) => void; +} + +export const SubIssues: React.FC = ({ + workspaceSlug, + projectId, + parentIssue, + issue, + spacingLeft = 0, + user, + editable, + removeIssueFromSubIssues, + issuesVisibility, + handleIssuesVisibility, + copyText, +}) => ( +
+ {issue && ( +
+
+ {issue?.sub_issues_count > 0 && ( + <> + {true ? ( +
handleIssuesVisibility(issue?.id)} + > + {issuesVisibility && issuesVisibility.includes(issue?.id) ? ( + + ) : ( + + )} +
+ ) : ( + + )} + + )} +
+ + + +
+
+ {issue.project_detail.identifier}-{issue?.sequence_id} +
+
+ {issue?.name} +
+
+ + +
+ +
+ +
+ + {/* {editable && ( + editIssue()}> +
+ + Edit issue +
+
+ )} */} + + {/* {editable && ( + handleDeleteIssue(issue)}> +
+ + Delete issue +
+
+ )} */} + + +
+ + Copy issue link +
+
+
+
+ + {editable && ( +
removeIssueFromSubIssues(parentIssue?.id, issue)} + > + +
+ )} +
+ )} + + {issuesVisibility.includes(issue?.id) && issue?.sub_issues_count > 0 && ( + + )} +
+); diff --git a/web/components/issues/sub-issues/issues-list.tsx b/web/components/issues/sub-issues/issues-list.tsx new file mode 100644 index 00000000000..0ebba72373d --- /dev/null +++ b/web/components/issues/sub-issues/issues-list.tsx @@ -0,0 +1,71 @@ +import React from "react"; +// swr +import useSWR from "swr"; +// components +import { SubIssues } from "./issue"; +// types +import { ICurrentUserResponse, IIssue } from "types"; +// services +import issuesService from "services/issues.service"; +// fetch keys +import { SUB_ISSUES } from "constants/fetch-keys"; + +export interface ISubIssuesRootList { + workspaceSlug: string; + projectId: string; + parentIssue: IIssue; + spacingLeft?: number; + user: ICurrentUserResponse | undefined; + editable: boolean; + removeIssueFromSubIssues: (parentIssueId: string, issue: IIssue) => void; + issuesVisibility: string[]; + handleIssuesVisibility: (issueId: string) => void; + copyText: (text: string) => void; +} + +export const SubIssuesRootList: React.FC = ({ + workspaceSlug, + projectId, + parentIssue, + spacingLeft = 0, + user, + editable, + removeIssueFromSubIssues, + issuesVisibility, + handleIssuesVisibility, + copyText, +}) => { + const { data: issues, isLoading } = useSWR( + workspaceSlug && projectId && parentIssue && parentIssue?.id + ? SUB_ISSUES(parentIssue?.id) + : null, + workspaceSlug && projectId && parentIssue && parentIssue?.id + ? () => issuesService.subIssues(workspaceSlug, projectId, parentIssue.id) + : null + ); + + console.log("isLoading", isLoading); + + return ( + <> + {issues && + issues.sub_issues.length > 0 && + issues.sub_issues.map((issue: IIssue) => ( + + ))} + + ); +}; diff --git a/web/components/issues/sub-issues/progressbar.tsx b/web/components/issues/sub-issues/progressbar.tsx new file mode 100644 index 00000000000..368078a3d09 --- /dev/null +++ b/web/components/issues/sub-issues/progressbar.tsx @@ -0,0 +1,25 @@ +export interface IProgressBar { + total: number; + done: number; +} + +export const ProgressBar = ({ total = 0, done = 0 }: IProgressBar) => { + const calPercentage = (doneValue: number, totalValue: number): string => { + if (doneValue === 0 || totalValue === 0) return (0).toFixed(0); + return ((100 * doneValue) / totalValue).toFixed(0); + }; + + return ( +
+
+
+
+
+
+
{calPercentage(done, total)}% Done
+
+ ); +}; diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx new file mode 100644 index 00000000000..a899efdcfb6 --- /dev/null +++ b/web/components/issues/sub-issues/properties.tsx @@ -0,0 +1,204 @@ +import React from "react"; +// swr +import { mutate } from "swr"; +// components +import { ViewDueDateSelect, ViewStartDateSelect } from "components/issues"; +import { MembersSelect, PrioritySelect } from "components/project"; +import { StateSelect } from "components/states"; +// hooks +import useIssuesProperties from "hooks/use-issue-properties"; +// types +import { ICurrentUserResponse, IIssue, IState } from "types"; +// fetch-keys +import { SUB_ISSUES } from "constants/fetch-keys"; +// services +import issuesService from "services/issues.service"; +import trackEventServices from "services/track-event.service"; + +export interface IIssueProperty { + workspaceSlug: string; + projectId: string; + parentIssue: IIssue; + issue: IIssue; + user: ICurrentUserResponse | undefined; + editable: boolean; +} + +export const IssueProperty: React.FC = ({ + workspaceSlug, + projectId, + parentIssue, + issue, + user, + editable, +}) => { + const [properties] = useIssuesProperties(workspaceSlug, projectId); + + const handlePriorityChange = (data: any) => { + partialUpdateIssue({ priority: data }); + trackEventServices.trackIssuePartialPropertyUpdateEvent( + { + workspaceSlug, + workspaceId: issue.workspace, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + "ISSUE_PROPERTY_UPDATE_PRIORITY", + user + ); + }; + + const handleStateChange = (data: string, states: IState[] | undefined) => { + const oldState = states?.find((s) => s.id === issue.state); + const newState = states?.find((s) => s.id === data); + + partialUpdateIssue({ + state: data, + state_detail: newState, + }); + trackEventServices.trackIssuePartialPropertyUpdateEvent( + { + workspaceSlug, + workspaceId: issue.workspace, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + "ISSUE_PROPERTY_UPDATE_STATE", + user + ); + if (oldState?.group !== "completed" && newState?.group !== "completed") { + trackEventServices.trackIssueMarkedAsDoneEvent( + { + workspaceSlug: issue.workspace_detail.slug, + workspaceId: issue.workspace_detail.id, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + user + ); + } + }; + + const handleAssigneeChange = (data: any) => { + const newData = issue.assignees ?? []; + + if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); + else newData.push(data); + + partialUpdateIssue({ assignees_list: data }); + + trackEventServices.trackIssuePartialPropertyUpdateEvent( + { + workspaceSlug, + workspaceId: issue.workspace, + projectId: issue.project_detail.id, + projectIdentifier: issue.project_detail.identifier, + projectName: issue.project_detail.name, + issueId: issue.id, + }, + "ISSUE_PROPERTY_UPDATE_ASSIGNEE", + user + ); + }; + + const partialUpdateIssue = async (data: Partial) => { + mutate( + workspaceSlug && parentIssue ? SUB_ISSUES(parentIssue.id) : null, + (elements: any) => { + const _elements = { ...elements }; + const _issues = _elements.sub_issues.map((element: IIssue) => + element.id === issue.id ? { ...element, ...data } : element + ); + _elements["sub_issues"] = [..._issues]; + return _elements; + }, + false + ); + + const issueResponse = await issuesService.patchIssue( + workspaceSlug as string, + issue.project, + issue.id, + data, + user + ); + + mutate( + SUB_ISSUES(parentIssue.id), + (elements: any) => { + const _elements = elements.sub_issues.map((element: IIssue) => + element.id === issue.id ? issueResponse : element + ); + elements["sub_issues"] = _elements; + return elements; + }, + true + ); + }; + + return ( +
+ {properties.priority && ( +
+ +
+ )} + + {properties.state && ( +
+ +
+ )} + + {properties.start_date && issue.start_date && ( +
+ +
+ )} + + {properties.due_date && issue.target_date && ( +
+ +
+ )} + + {properties.assignee && ( +
+ +
+ )} +
+ ); +}; diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx new file mode 100644 index 00000000000..5fd868064e8 --- /dev/null +++ b/web/components/issues/sub-issues/root.tsx @@ -0,0 +1,239 @@ +import React from "react"; +// next imports +import { useRouter } from "next/router"; +// swr +import useSWR, { mutate } from "swr"; +// lucide icons +import { Plus, ChevronRight, ChevronDown } from "lucide-react"; +// components +import { ExistingIssuesListModal } from "components/core"; +import { CreateUpdateIssueModal } from "components/issues"; +import { SubIssuesRootList } from "./issues-list"; +import { ProgressBar } from "./progressbar"; +// ui +import { CustomMenu } from "components/ui"; +// hooks +import { useProjectMyMembership } from "contexts/project-member.context"; +// helpers +import { copyTextToClipboard } from "helpers/string.helper"; +// types +import { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types"; +// services +import issuesService from "services/issues.service"; +// fetch keys +import { SUB_ISSUES } from "constants/fetch-keys"; + +export interface ISubIssuesRoot { + parentIssue: IIssue; + + user: ICurrentUserResponse | undefined; + editable: boolean; +} + +export const SubIssuesRoot: React.FC = ({ parentIssue, user, editable }) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; + + const { memberRole } = useProjectMyMembership(); + + const { data: issues } = useSWR( + workspaceSlug && projectId && parentIssue && parentIssue?.id + ? SUB_ISSUES(parentIssue?.id) + : null, + workspaceSlug && projectId && parentIssue && parentIssue?.id + ? () => issuesService.subIssues(workspaceSlug, projectId, parentIssue.id) + : null + ); + + const [issuesVisibility, setIssuesVisibility] = React.useState([parentIssue?.id]); + const handleIssuesVisibility = (issueId: string) => { + if (issuesVisibility.includes(issueId)) { + setIssuesVisibility(issuesVisibility.filter((i: string) => i !== issueId)); + } else { + setIssuesVisibility([...issuesVisibility, issueId]); + } + }; + + const [issueCreateOption, setIssueCreateOption] = React.useState<{ + create: { toggle: boolean; issueId: string | null }; + existing: { toggle: boolean; issueId: string | null }; + }>({ + create: { + toggle: false, + issueId: null, + }, + existing: { + toggle: false, + issueId: null, + }, + }); + const handleCreateOption = (key: "create" | "existing", issueId: string | null) => { + setIssueCreateOption({ + ...issueCreateOption, + [key]: { + toggle: !issueCreateOption[key].toggle, + issueId: issueId, + }, + }); + }; + + const addAsSubIssueFromExistingIssues = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !parentIssue || issueCreateOption?.existing?.issueId === null) return; + const issueId = issueCreateOption?.existing?.issueId; + const payload = { + sub_issue_ids: data.map((i) => i.id), + }; + + await issuesService.addSubIssues(workspaceSlug, projectId, issueId, payload).finally(() => { + if (issueId) mutate(SUB_ISSUES(issueId), true); + }); + }; + + const removeIssueFromSubIssues = async (parentIssueId: string, issue: IIssue) => { + if (!workspaceSlug || !parentIssue || !issue?.id) return; + issuesService + .patchIssue(workspaceSlug, projectId, issue.id, { parent: null }, user) + .finally(() => { + if (parentIssueId) mutate(SUB_ISSUES(parentIssueId), true); + }); + }; + + const copyText = (text: string) => { + const originURL = + typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + copyTextToClipboard(`${originURL}/${text}`).then(() => { + // setToastAlert({ + // type: "success", + // title: "Link Copied!", + // message: "Issue link copied to clipboard.", + // }); + }); + }; + + const isEditable = memberRole?.isGuest || memberRole?.isViewer ? false : true; + + console.log("isEditable", isEditable); + + return ( +
+ {parentIssue && parentIssue?.sub_issues_count > 0 ? ( + <> + {/* header */} +
+
handleIssuesVisibility(parentIssue?.id)} + > +
+ {issuesVisibility.includes(parentIssue?.id) ? ( + + ) : ( + + )} +
+
+
Sub-issues
+
({parentIssue?.sub_issues_count})
+
+
+ +
+ +
+ + {isEditable && issuesVisibility.includes(parentIssue?.id) && ( +
+
handleCreateOption("create", parentIssue?.id)} + > + Add sub-issue +
+
handleCreateOption("existing", parentIssue?.id)} + > + Add an existing issue +
+
+ )} +
+ + {/* issues */} +
+ {issuesVisibility.includes(parentIssue?.id) && ( + + )} +
+ + ) : ( + isEditable && ( +
+
No sub issues are available
+ <> + + + Add sub-issue + + } + buttonClassName="whitespace-nowrap" + position="left" + noBorder + noChevron + > + handleCreateOption("create", parentIssue?.id)}> + Create new + + handleCreateOption("existing", parentIssue?.id)} + > + Add an existing issue + + + +
+ ) + )} + + {isEditable && issueCreateOption?.create?.toggle && issueCreateOption?.create?.issueId && ( + handleCreateOption("create", null)} + /> + )} + + {isEditable && + issueCreateOption?.existing?.toggle && + issueCreateOption?.existing?.issueId && ( + handleCreateOption("existing", null)} + searchParams={{ sub_issue: true, issue_id: issueCreateOption?.existing?.issueId }} + handleOnSubmit={addAsSubIssueFromExistingIssues} + workspaceLevelToggle + /> + )} +
+ ); +}; diff --git a/web/components/project/members-select.tsx b/web/components/project/members-select.tsx index f99d8517488..4ffad72b9fe 100644 --- a/web/components/project/members-select.tsx +++ b/web/components/project/members-select.tsx @@ -82,7 +82,7 @@ export const MembersSelect: React.FC = ({ 0 + membersDetails && membersDetails.length > 0 ? membersDetails.map((assignee) => assignee?.display_name).join(", ") : "No Assignee" } From 08a1c23cfcc93c004ad1c3cd637ed4ec7c4c76e7 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 21 Sep 2023 14:20:45 +0530 Subject: [PATCH 2/4] feat: updated UI for sub issues --- web/components/issues/sub-issues/issue.tsx | 16 +++++++--------- .../issues/sub-issues/issues-list.tsx | 15 ++++++++++----- web/components/issues/sub-issues/root.tsx | 18 ++++++++---------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/web/components/issues/sub-issues/issue.tsx b/web/components/issues/sub-issues/issue.tsx index bca67fa043d..a7e030e98f1 100644 --- a/web/components/issues/sub-issues/issue.tsx +++ b/web/components/issues/sub-issues/issue.tsx @@ -49,10 +49,10 @@ export const SubIssues: React.FC = ({
{issue && (
-
+
{issue?.sub_issues_count > 0 && ( <> {true ? ( @@ -84,9 +84,7 @@ export const SubIssues: React.FC = ({
{issue.project_detail.identifier}-{issue?.sequence_id}
-
- {issue?.name} -
+
{issue?.name}
@@ -103,23 +101,23 @@ export const SubIssues: React.FC = ({
- {/* {editable && ( + {editable && ( editIssue()}>
Edit issue
- )} */} + )} - {/* {editable && ( + {editable && ( handleDeleteIssue(issue)}>
Delete issue
- )} */} + )}
diff --git a/web/components/issues/sub-issues/issues-list.tsx b/web/components/issues/sub-issues/issues-list.tsx index 0ebba72373d..e451a9c2e50 100644 --- a/web/components/issues/sub-issues/issues-list.tsx +++ b/web/components/issues/sub-issues/issues-list.tsx @@ -27,7 +27,7 @@ export const SubIssuesRootList: React.FC = ({ workspaceSlug, projectId, parentIssue, - spacingLeft = 0, + spacingLeft = 10, user, editable, removeIssueFromSubIssues, @@ -44,10 +44,8 @@ export const SubIssuesRootList: React.FC = ({ : null ); - console.log("isLoading", isLoading); - return ( - <> +
{issues && issues.sub_issues.length > 0 && issues.sub_issues.map((issue: IIssue) => ( @@ -66,6 +64,13 @@ export const SubIssuesRootList: React.FC = ({ copyText={copyText} /> ))} - + +
10 ? `border-l border-custom-border-100` : `` + }`} + style={{ left: `${spacingLeft - 12}px` }} + /> +
); }; diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 5fd868064e8..dfc7e61b395 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -121,20 +121,18 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi {/* header */}
handleIssuesVisibility(parentIssue?.id)} >
- {issuesVisibility.includes(parentIssue?.id) ? ( + {!issuesVisibility.includes(parentIssue?.id) ? ( ) : ( )}
-
-
Sub-issues
-
({parentIssue?.sub_issues_count})
-
+
Sub-issues
+
({parentIssue?.sub_issues_count})
@@ -166,8 +164,8 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi
{/* issues */} -
- {issuesVisibility.includes(parentIssue?.id) && ( + {issuesVisibility.includes(parentIssue?.id) && ( +
= ({ parentIssue, user, edi handleIssuesVisibility={handleIssuesVisibility} copyText={copyText} /> - )} -
+
+ )} ) : ( isEditable && ( From 9b7c8da1de726dac68cf99e272bbba48e22e69b5 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 21 Sep 2023 15:23:01 +0530 Subject: [PATCH 3/4] feat: subissues new ui and nested sub issues in issue detail --- web/components/issues/delete-issue-modal.tsx | 4 +- web/components/issues/sub-issues/issue.tsx | 24 ++++- .../issues/sub-issues/issues-list.tsx | 8 ++ web/components/issues/sub-issues/root.tsx | 96 ++++++++++++++----- 4 files changed, 101 insertions(+), 31 deletions(-) diff --git a/web/components/issues/delete-issue-modal.tsx b/web/components/issues/delete-issue-modal.tsx index d76e5ddd2ed..62fc0472348 100644 --- a/web/components/issues/delete-issue-modal.tsx +++ b/web/components/issues/delete-issue-modal.tsx @@ -35,6 +35,7 @@ type Props = { data: IIssue | null; user: ICurrentUserResponse | undefined; onSubmit?: () => Promise; + redirection?: boolean; }; export const DeleteIssueModal: React.FC = ({ @@ -43,6 +44,7 @@ export const DeleteIssueModal: React.FC = ({ data, user, onSubmit, + redirection = true, }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); @@ -132,7 +134,7 @@ export const DeleteIssueModal: React.FC = ({ message: "Issue deleted successfully", }); - if (issueId) router.back(); + if (issueId && redirection) router.back(); }) .catch((error) => { console.log(error); diff --git a/web/components/issues/sub-issues/issue.tsx b/web/components/issues/sub-issues/issue.tsx index a7e030e98f1..2e3d8acdbb3 100644 --- a/web/components/issues/sub-issues/issue.tsx +++ b/web/components/issues/sub-issues/issue.tsx @@ -15,7 +15,8 @@ import { import { SubIssuesRootList } from "./issues-list"; import { IssueProperty } from "./properties"; // ui -import { CustomMenu } from "components/ui"; +import { Tooltip, CustomMenu } from "components/ui"; + // types import { ICurrentUserResponse, IIssue } from "types"; @@ -31,6 +32,11 @@ export interface ISubIssues { issuesVisibility: string[]; handleIssuesVisibility: (issueId: string) => void; copyText: (text: string) => void; + handleIssueCrudOperation: ( + key: "create" | "existing" | "edit" | "delete", + issueId: string, + issue?: IIssue | null + ) => void; } export const SubIssues: React.FC = ({ @@ -45,6 +51,7 @@ export const SubIssues: React.FC = ({ issuesVisibility, handleIssuesVisibility, copyText, + handleIssueCrudOperation, }) => (
{issue && ( @@ -73,7 +80,7 @@ export const SubIssues: React.FC = ({ )}
- +
= ({
{issue.project_detail.identifier}-{issue?.sequence_id}
-
{issue?.name}
+ +
{issue?.name}
+
@@ -102,7 +111,9 @@ export const SubIssues: React.FC = ({
{editable && ( - editIssue()}> + handleIssueCrudOperation("edit", parentIssue?.id, issue)} + >
Edit issue @@ -111,7 +122,9 @@ export const SubIssues: React.FC = ({ )} {editable && ( - handleDeleteIssue(issue)}> + handleIssueCrudOperation("delete", parentIssue?.id, issue)} + >
Delete issue @@ -151,6 +164,7 @@ export const SubIssues: React.FC = ({ issuesVisibility={issuesVisibility} handleIssuesVisibility={handleIssuesVisibility} copyText={copyText} + handleIssueCrudOperation={handleIssueCrudOperation} /> )}
diff --git a/web/components/issues/sub-issues/issues-list.tsx b/web/components/issues/sub-issues/issues-list.tsx index e451a9c2e50..45c0b18828f 100644 --- a/web/components/issues/sub-issues/issues-list.tsx +++ b/web/components/issues/sub-issues/issues-list.tsx @@ -21,6 +21,11 @@ export interface ISubIssuesRootList { issuesVisibility: string[]; handleIssuesVisibility: (issueId: string) => void; copyText: (text: string) => void; + handleIssueCrudOperation: ( + key: "create" | "existing" | "edit" | "delete", + issueId: string, + issue?: IIssue | null + ) => void; } export const SubIssuesRootList: React.FC = ({ @@ -34,6 +39,7 @@ export const SubIssuesRootList: React.FC = ({ issuesVisibility, handleIssuesVisibility, copyText, + handleIssueCrudOperation, }) => { const { data: issues, isLoading } = useSWR( workspaceSlug && projectId && parentIssue && parentIssue?.id @@ -47,6 +53,7 @@ export const SubIssuesRootList: React.FC = ({ return (
{issues && + issues.sub_issues && issues.sub_issues.length > 0 && issues.sub_issues.map((issue: IIssue) => ( = ({ issuesVisibility={issuesVisibility} handleIssuesVisibility={handleIssuesVisibility} copyText={copyText} + handleIssueCrudOperation={handleIssueCrudOperation} /> ))} diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index dfc7e61b395..75161e63964 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -7,7 +7,7 @@ import useSWR, { mutate } from "swr"; import { Plus, ChevronRight, ChevronDown } from "lucide-react"; // components import { ExistingIssuesListModal } from "components/core"; -import { CreateUpdateIssueModal } from "components/issues"; +import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; import { SubIssuesRootList } from "./issues-list"; import { ProgressBar } from "./progressbar"; // ui @@ -54,9 +54,11 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi } }; - const [issueCreateOption, setIssueCreateOption] = React.useState<{ + const [issueCrudOperation, setIssueCrudOperation] = React.useState<{ create: { toggle: boolean; issueId: string | null }; existing: { toggle: boolean; issueId: string | null }; + edit: { toggle: boolean; issueId: string | null; issue: IIssue | null }; + delete: { toggle: boolean; issueId: string | null; issue: IIssue | null }; }>({ create: { toggle: false, @@ -66,26 +68,41 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi toggle: false, issueId: null, }, + edit: { + toggle: false, + issueId: null, // parent issue id for mutation + issue: null, + }, + delete: { + toggle: false, + issueId: null, // parent issue id for mutation + issue: null, + }, }); - const handleCreateOption = (key: "create" | "existing", issueId: string | null) => { - setIssueCreateOption({ - ...issueCreateOption, + const handleIssueCrudOperation = ( + key: "create" | "existing" | "edit" | "delete", + issueId: string | null, + issue: IIssue | null = null + ) => { + setIssueCrudOperation({ + ...issueCrudOperation, [key]: { - toggle: !issueCreateOption[key].toggle, + toggle: !issueCrudOperation[key].toggle, issueId: issueId, + issue: issue, }, }); }; const addAsSubIssueFromExistingIssues = async (data: ISearchIssueResponse[]) => { - if (!workspaceSlug || !parentIssue || issueCreateOption?.existing?.issueId === null) return; - const issueId = issueCreateOption?.existing?.issueId; + if (!workspaceSlug || !parentIssue || issueCrudOperation?.existing?.issueId === null) return; + const issueId = issueCrudOperation?.existing?.issueId; const payload = { sub_issue_ids: data.map((i) => i.id), }; await issuesService.addSubIssues(workspaceSlug, projectId, issueId, payload).finally(() => { - if (issueId) mutate(SUB_ISSUES(issueId), true); + if (issueId) mutate(SUB_ISSUES(issueId)); }); }; @@ -94,7 +111,7 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi issuesService .patchIssue(workspaceSlug, projectId, issue.id, { parent: null }, user) .finally(() => { - if (parentIssueId) mutate(SUB_ISSUES(parentIssueId), true); + if (parentIssueId) mutate(SUB_ISSUES(parentIssueId)); }); }; @@ -112,7 +129,9 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi const isEditable = memberRole?.isGuest || memberRole?.isViewer ? false : true; - console.log("isEditable", isEditable); + const mutateSubIssues = (parentIssueId: string | null) => { + if (parentIssueId) mutate(SUB_ISSUES(parentIssueId)); + }; return (
@@ -125,7 +144,7 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi onClick={() => handleIssuesVisibility(parentIssue?.id)} >
- {!issuesVisibility.includes(parentIssue?.id) ? ( + {issuesVisibility.includes(parentIssue?.id) ? ( ) : ( @@ -149,13 +168,13 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi
handleCreateOption("create", parentIssue?.id)} + onClick={() => handleIssueCrudOperation("create", parentIssue?.id)} > Add sub-issue
handleCreateOption("existing", parentIssue?.id)} + onClick={() => handleIssueCrudOperation("existing", parentIssue?.id)} > Add an existing issue
@@ -176,6 +195,7 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi issuesVisibility={issuesVisibility} handleIssuesVisibility={handleIssuesVisibility} copyText={copyText} + handleIssueCrudOperation={handleIssueCrudOperation} />
)} @@ -197,11 +217,13 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi noBorder noChevron > - handleCreateOption("create", parentIssue?.id)}> + handleIssueCrudOperation("create", parentIssue?.id)} + > Create new handleCreateOption("existing", parentIssue?.id)} + onClick={() => handleIssueCrudOperation("existing", parentIssue?.id)} > Add an existing issue @@ -211,27 +233,51 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user, edi ) )} - {isEditable && issueCreateOption?.create?.toggle && issueCreateOption?.create?.issueId && ( + {isEditable && issueCrudOperation?.create?.toggle && ( handleCreateOption("create", null)} + handleClose={() => handleIssueCrudOperation("create", null)} /> )} {isEditable && - issueCreateOption?.existing?.toggle && - issueCreateOption?.existing?.issueId && ( + issueCrudOperation?.existing?.toggle && + issueCrudOperation?.existing?.issueId && ( handleCreateOption("existing", null)} - searchParams={{ sub_issue: true, issue_id: issueCreateOption?.existing?.issueId }} + isOpen={issueCrudOperation?.existing?.toggle} + handleClose={() => handleIssueCrudOperation("existing", null)} + searchParams={{ sub_issue: true, issue_id: issueCrudOperation?.existing?.issueId }} handleOnSubmit={addAsSubIssueFromExistingIssues} workspaceLevelToggle /> )} + + {isEditable && issueCrudOperation?.edit?.toggle && issueCrudOperation?.edit?.issueId && ( + { + mutateSubIssues(issueCrudOperation?.edit?.issueId); + handleIssueCrudOperation("edit", null, null); + }} + data={issueCrudOperation?.edit?.issue} + /> + )} + + {isEditable && issueCrudOperation?.delete?.toggle && issueCrudOperation?.delete?.issueId && ( + { + mutateSubIssues(issueCrudOperation?.delete?.issueId); + handleIssueCrudOperation("delete", null, null); + }} + data={issueCrudOperation?.delete?.issue} + user={user} + redirection={false} + /> + )}
); }; From 6bb9b73711a54d20521ccb16b56ebd35a25bd273 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 21 Sep 2023 15:31:41 +0530 Subject: [PATCH 4/4] chore: removed repeated code --- web/components/issues/index.ts | 1 - web/components/issues/main-content.tsx | 2 - web/components/issues/sub-issues-list.tsx | 251 ---------------------- 3 files changed, 254 deletions(-) delete mode 100644 web/components/issues/sub-issues-list.tsx diff --git a/web/components/issues/index.ts b/web/components/issues/index.ts index 1c51031f36c..6b83e7ef438 100644 --- a/web/components/issues/index.ts +++ b/web/components/issues/index.ts @@ -12,7 +12,6 @@ export * from "./main-content"; export * from "./modal"; export * from "./parent-issues-list-modal"; export * from "./sidebar"; -export * from "./sub-issues-list"; export * from "./label"; export * from "./issue-reaction"; export * from "./peek-overview"; diff --git a/web/components/issues/main-content.tsx b/web/components/issues/main-content.tsx index d1f59e8ef53..5e14ba4326e 100644 --- a/web/components/issues/main-content.tsx +++ b/web/components/issues/main-content.tsx @@ -18,7 +18,6 @@ import { IssueAttachmentUpload, IssueAttachments, IssueDescriptionForm, - SubIssuesList, IssueReaction, } from "components/issues"; import { SubIssuesRoot } from "./sub-issues"; @@ -208,7 +207,6 @@ export const IssueMainContent: React.FC = ({
- {/* */}
diff --git a/web/components/issues/sub-issues-list.tsx b/web/components/issues/sub-issues-list.tsx deleted file mode 100644 index 9ba920ff53f..00000000000 --- a/web/components/issues/sub-issues-list.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { FC, useState } from "react"; - -import Link from "next/link"; -import { useRouter } from "next/router"; - -import useSWR, { mutate } from "swr"; - -// headless ui -import { Disclosure, Transition } from "@headlessui/react"; -// services -import issuesService from "services/issues.service"; -// contexts -import { useProjectMyMembership } from "contexts/project-member.context"; -// components -import { ExistingIssuesListModal } from "components/core"; -import { CreateUpdateIssueModal } from "components/issues"; -// ui -import { CustomMenu } from "components/ui"; -// icons -import { ChevronRightIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; -// types -import { ICurrentUserResponse, IIssue, ISearchIssueResponse, ISubIssueResponse } from "types"; -// fetch-keys -import { SUB_ISSUES } from "constants/fetch-keys"; - -type Props = { - parentIssue: IIssue; - user: ICurrentUserResponse | undefined; - disabled?: boolean; -}; - -export const SubIssuesList: FC = ({ parentIssue, user, disabled = false }) => { - // states - const [createIssueModal, setCreateIssueModal] = useState(false); - const [subIssuesListModal, setSubIssuesListModal] = useState(false); - const [preloadedData, setPreloadedData] = useState | null>(null); - - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { memberRole } = useProjectMyMembership(); - - const { data: subIssuesResponse } = useSWR( - workspaceSlug && parentIssue ? SUB_ISSUES(parentIssue.id) : null, - workspaceSlug && parentIssue - ? () => issuesService.subIssues(workspaceSlug as string, parentIssue.project, parentIssue.id) - : null - ); - - const addAsSubIssue = async (data: ISearchIssueResponse[]) => { - if (!workspaceSlug || !parentIssue) return; - - const payload = { - sub_issue_ids: data.map((i) => i.id), - }; - - await issuesService - .addSubIssues(workspaceSlug as string, parentIssue.project, parentIssue.id, payload) - .finally(() => mutate(SUB_ISSUES(parentIssue.id))); - }; - - const handleSubIssueRemove = (issue: IIssue) => { - if (!workspaceSlug || !parentIssue) return; - - mutate( - SUB_ISSUES(parentIssue.id), - (prevData) => { - if (!prevData) return prevData; - - const stateDistribution = { ...prevData.state_distribution }; - - const issueGroup = issue.state_detail.group; - stateDistribution[issueGroup] = stateDistribution[issueGroup] - 1; - - return { - state_distribution: stateDistribution, - sub_issues: prevData.sub_issues.filter((i) => i.id !== issue.id), - }; - }, - false - ); - - issuesService - .patchIssue(workspaceSlug.toString(), issue.project, issue.id, { parent: null }, user) - .finally(() => mutate(SUB_ISSUES(parentIssue.id))); - }; - - const handleCreateIssueModal = () => { - setCreateIssueModal(true); - - setPreloadedData({ - parent: parentIssue.id, - }); - }; - - const completedSubIssue = subIssuesResponse?.state_distribution.completed ?? 0; - const cancelledSubIssue = subIssuesResponse?.state_distribution.cancelled ?? 0; - - const totalCompletedSubIssues = completedSubIssue + cancelledSubIssue; - - const totalSubIssues = subIssuesResponse ? subIssuesResponse.sub_issues.length : 0; - - const completionPercentage = (totalCompletedSubIssues / totalSubIssues) * 100; - - const isNotAllowed = memberRole.isGuest || memberRole.isViewer || disabled; - - return ( - <> - setCreateIssueModal(false)} - /> - setSubIssuesListModal(false)} - searchParams={{ sub_issue: true, issue_id: parentIssue?.id }} - handleOnSubmit={addAsSubIssue} - workspaceLevelToggle - /> - {subIssuesResponse && subIssuesResponse.sub_issues.length > 0 ? ( - - {({ open }) => ( - <> -
-
- - - Sub-issues{" "} - - {subIssuesResponse.sub_issues.length} - - -
-
-
100 - ? 100 - : completionPercentage.toFixed(0) - }%`, - }} - /> -
- - {isNaN(completionPercentage) - ? 0 - : completionPercentage > 100 - ? 100 - : completionPercentage.toFixed(0)} - % Done - -
-
- - {open && !isNotAllowed ? ( -
- - - - setSubIssuesListModal(true)}> - Add an existing issue - - -
- ) : null} -
- - - {subIssuesResponse.sub_issues.map((issue) => ( - - -
- - - {issue.project_detail.identifier}-{issue.sequence_id} - - {issue.name} -
- - {!isNotAllowed && ( - - )} -
- - ))} -
-
- - )} - - ) : ( - !isNotAllowed && ( - - - Add sub-issue - - } - buttonClassName="whitespace-nowrap" - position="left" - noBorder - noChevron - > - Create new - setSubIssuesListModal(true)}> - Add an existing issue - - - ) - )} - - ); -};