plane/web/components/issues/sub-issues/root.tsx
sriram veeraghanta 5d807db69e
feat: app dir migration (#4743)
* feat: creating new app dir structure for web app

* fix: moving few pages to app dir

* fix: adding profile settings layout

* fix: errors on app dir.

* chore: remove pages routes.

* chore: add sign-in/ sign-up, invitations, onboarding pages.

* [WEB-1374] fix: clear changes made on modal close (#4555)

* [WEB-1480] fix: preserve page access when making a copy (#4568)

* [WEB-1465] fix: theme fluctuation on initial load. (#4638)

* [WEB-1445] fix: issue creation on sub groups when cycle/ module grouping is applied. (#4636)

* [WEB-1244] fix: add better image insertion and replacement logic in the editor (#4508)

* fix: add better image insertion and replacement logic

* refactor: image handling in editor

* chore: remove passing uploadKey around

* refactor: remove unused code

* fix: redundant files removed

* fix: add is editor ready to discard api to control behvaiours from our app

* fix: focus issues and image insertion position when not using slash command

* fix: import order fixed

* fix: notification mark all as read (#4643)

* chore: remove enter key extension (#4648)

* [WEB-1467] chore: run the API's required to bootstrap the application in parallel. (#4642)

* [WEB - 1482] fix: uploads when using block storages other than s3 and minio (#4647)

* fix: minio storage and redirection

* dev: disconnect web url and app base url configuration.

* fix: negate check while trying to discard (#4653)

* fix: email notification preferences (#4656)

* [WEB-1493] chore: product tour asset and app sidebar quick action hover (#4655)

* chore: product tour asset updated

* fix: app sidebar quick action hover

* fix: project state setting state name remove camel case logic (#4652)

* [WEB-1419] chore: enable module creation with dates older than today. (#4659)

* [WEB-1216] chore: increase module empty state for consistency. (#4658)

* fix: build errors

* [WEB-1235] chore: module and cycle sidebar graph improvement (#4650)

* chore: module and cycle sidebar graph improvement

* chore: code refactor

* [WEB-1424] chore: page and view logo implementation, and emoji/icon (#4662)

* [WEB-1424] chore: page and view logo implementation, and emoji/icon picker improvement (#4583)

* chore: added logo_props

* chore: logo props in cycles, views and modules

* chore: emoji icon picker types updated

* chore: info icon added to plane ui package

* chore: icon color adjust helper function added

* style: icon picker ui improvement and default color options updated

* chore: update page logo action added in store

* chore: emoji code to unicode helper function added

* chore: common logo renderer component added

* chore: app header project logo updated

* chore: project logo updated across platform

* chore: page logo picker added

* chore: control link component improvement

* chore: list item improvement

* chore: emoji picker component updated

* chore: space app and package logo prop type updated

* chore: migration

* chore: logo added to project view

* chore: page logo picker added in create modal and breadcrumbs

* chore: view logo picker added in create modal and updated breadcrumbs

* fix: build error

* chore: AIO docker images for preview deployments (#4605)

* fix: adding single docker base file

* action added

* fix action

* dockerfile.base modified

* action fix

* dockerfile

* fix: base aio dockerfile

* fix: dockerfile.base

* fix: dockerfile base

* fix: modified folder structure

* fix: action

* fix: dockerfile

* fix: dockerfile.base

* fix: supervisor file name changed

* fix: base dockerfile updated

* fix dockerfile base

* fix: base dockerfile

* fix: docker files

* fix: base dockerfile

* update base image

* modified docker aio base

* aio base modified to debian-12-slim

* fixes

* finalize the dockerfiles with volume exposure

* modified the aio build and dockerfile

* fix: codacy suggestions implemented

* fix: codacy fix

* update aio build action

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* fix: merge conflict

* chore: lucide react added to planu ui package

* chore: new emoji picker component added with lucid icon and code refactor

* chore: logo component updated

* chore: emoji picker updated for pages and views

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* fix: build error

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>

* refactor: drag handle component (#4663)

* refactor: checkbox ui component (#4665)

* [WEB-1325] chore: refactor inbox issue store to avoid data loss. (#4640)

* [WEB-1325] chore: refactor inbox issue store to avoid data loss.

* chore: inbox store improvement.

* chore: priority dropdown accepts undefined (#4666)

* chore: added buttonClassName prop to label dropdown (#4671)

* chore: created new constants for marketing website page links (#4670)

* chore: added a prop to render default state conditionally (#4669)

* [WEB-1501] dev: multiple select core components (#4667)

* dev: multiple select core components

* chore: added export statement

* chore: created a new constant for archivable state groups (#4668)

* chore: added primary variant to the alert modal (#4664)

* [WEB-1436] chore: pages improvement. (#4657)

* add empty state if no pages are available.
* set access to private in create page modal when the modal is open form private tab.

* [WEB-1440] chore: update cycle empty state to use project level access. (#4672)

* fix: checkbox ui component (#4675)

* fix: ai buttons overlapping issue (#4621)

* [WEB - 1500] chore: add extra fields on instance and create changelog table to store release change logs (#4673)

* chore: add extra fields on instance and create changelog table to store release change logs

* dev: rename new_version to latest_version

* [WEB - 1505] chore: alter instance id field (#4676)

* chore: instance id

* dev: update to max length

* feat: creating new app dir structure for web app

* fix: moving few pages to app dir

* feat: creating new app dir structure for web app

* fix: moving few pages to app dir

* fix: errors on app dir.

* chore: remove pages routes.

* chore: add sign-in/ sign-up, invitations, onboarding pages.

* fix: instance serializer

* fix: instance register script  (#4681)

* fix: instance register script

* dev: remove api key and add latest version and current version in types

* [WEB-1492] fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add (#4682)

* fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add

* fix: updated braces in conditions

* fix: inbox issue store update logic. (#4683)

* chore: update package version

* [WEB-1184] feat: issue bulk operations (#4674)

* feat: issue bulk operations

* style: bulk operations action bar

* chore: remove edition separation

* style: fix overlapping of response container in AI popover. (#4684)

* [WEB-1498] style: fix comments reaction alignment. (#4686)

* [WEB-1503] chore: add `autofocus` to name field in inline create/ update state component. (#4685)

* [WEB-1312] fix: trim file name before uploading (#4661)

* fix: trim file name before uploading

* fix: check the cursor position before inserting image

* dev: add trimming for file assets

* dev: add filename validation above if

* dev: make the validation to 50 to support user uploads

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>

* [WEB-1481] fix: multiple API calls in inbox issues on closed issues tab. (#4691)

* fix: multiple API calls on scroll and closed issues tab.

* fix: pagination loader on initial load.

* feat: Add components required for estimates (#4690)

* Add sortable, radio and typography components

* Remove stray css classes

* Prevent drag of items from other draggable

* Minor cleanup

* Update yarn.lock

* Remove radio input component as it was build on
headless ui v2.0.0 and now we are using v1.7.0

* Fix build errors

* Update dependencies in use memo.

* [WEB-1521] chore: add configuration to enable/disable sign-ups. (#4697)

* fix: regenerating lock file

* fix: docker image build errors

* fix: remove `setupInterceptors` to avoid circular dependency.

* chore: migrate all `accounts` related routes.

* chore: migrate all `profiles` related routes.

* chore: workspace invitation and onboarding migration / fixes.

* chore: installation provider migrations.

* regression: focus changing issue with the peek overview editor (#4700)

* [WEB-1459] chore: save users all / favorite project list collapse state into localstorage. (#4701)

* [WEB-1501] chore: update selected entity details on entities list change (#4702)

* chore: update selected entity detials on entities list change

* chore: addd selectionHelpers as a prop

* [WEB-1517] chore: remove drag handle from list drag block (#4698)

* remove drag handle from list drag block

* align list group header with list item

* rearrange chevron for list subissues and rearrange spaces

* adding default draggable property to control link

* remove unnecessary dependencies for useEffect

* fix: email validation  (#4707)

* fix: email validation on complete login or sign up functionality

* dev: add try catch block

* dev: split up code

* dev: empty return

* fix: cache invalidation on new members invite (#4699)

* fix: build test pull request running on non draft PRs (#4708)

* fix: cache invalidation on new members invite (#4699)

* fix: add version max length (#4713)

* chore: migrations for `routing` hooks.

* [WEB-1533] chore: fix alignment issues in List and Spreadsheet view (#4714)

* fix alignment issues in List and Spreadsheet view

* fix spreadsheet indentation

* chore: migration for workspace dashboard/ views/ analytics/ settings and active-cycles.

* chore: handle undefined identifier case

* fix: Overflowing loader in issue edit modal (#4720)

* [WEB-1529] chore: workspace sidebar updates. (#4710)

* fix: temporary fix exiting lines with slashes (#4725)

* [WEB-1537] fix: inline code block size fixed for headers, etc (#4709)

* fix: inline code block size fixed for headers, etc

* feat: persisting focus accurately post converting the code block into text

* fix: typo in error handling

* [WEB-1526] feat: add auto merge behaviour to task lists and fix infinite backspace case (#4703)

* feat: add auto merge behaviour to task lists

* fix: unhandled cases for taskItem and taskList

* fix: css task list such that toggling task list doesn't shift things

* fix: task list jumps around while trying create/delete things in between two task lists

* fix: remove filtering for generic transactions i.e. transactions with some meta data while tying to join things

* chore: migration for profile activity along with headers refactor.

* [WEB-1201] dev: dropdowns (#4721)

* chore: lodash package added

* chore: dropdown key down hook added

* dev: dropdown component

* chore: build error and code refactor

* chore: readme file updated

* chore: added disabled prop to multiple select components (#4724)

* chore: added disabled prop to mutliple select group hoc

* style: fix empty space

* fix: don't add  as a sub-issue if parent has been removed (#4731)

* fix: member list item custom menu placement (#4729)

* [WEB-1535] chore: project logo picker improvement (#4718)

* chore: emoji icon picker improvement

* chore: emoji icon picker improvement

* fix: resolved border flicker on issue title (#4727)

* chore: profile activity empty state added (#4732)

* [WEB-1481] fix: inbox issue list update after changing issue status. (#4715)

* style: fix ux copy style on project feature preview page. (#4734)

* chore: remove clear seleciton logic on escape key press (#4735)

* chore: migrations for projects and project issues.

* chore: issue and properties filter dropdown improvement (#4733)

* save all filters and properties for views (#4728)

* chore: migrations for issue details route.

* chore: migration for cycle routes.

* chore: migration for module routes.

* chore: migrations for project views routes.

* chore: migrations for project pages routes.

* chore: migration for project inbox routes.

* chore: migration for project settings routes.

* chore: migrations for draft issues routes.

* chore: migrations for project archives routes.

* chore: remove unused headers.

* temp: comment out auth constant and use-reload-confirmation code to avoid errors.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2024-06-10 12:37:38 +05:30

547 lines
20 KiB
TypeScript

import { FC, useCallback, useEffect, useMemo, useState } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
// icons
import { Plus, ChevronRight, Loader, Pencil } from "lucide-react";
// types
import { IUser, TIssue } from "@plane/types";
// ui
import { CircularProgressIndicator, CustomMenu, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ExistingIssuesListModal } from "@/components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// helpers
import { cn } from "@/helpers/common.helper";
import { copyTextToClipboard } from "@/helpers/string.helper";
// hooks
import { useEventTracker, useIssueDetail } from "@/hooks/store";
// local components
import useURLHash from "@/hooks/use-url-hash";
import { IssueList } from "./issues-list";
export interface ISubIssuesRoot {
workspaceSlug: string;
projectId: string;
parentIssueId: string;
currentUser: IUser;
disabled: boolean;
}
export type TSubIssueOperations = {
copyText: (text: string) => void;
fetchSubIssues: (workspaceSlug: string, projectId: string, parentIssueId: string) => Promise<void>;
addSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueIds: string[]) => Promise<void>;
updateSubIssue: (
workspaceSlug: string,
projectId: string,
parentIssueId: string,
issueId: string,
issueData: Partial<TIssue>,
oldIssue?: Partial<TIssue>,
fromModal?: boolean
) => Promise<void>;
removeSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>;
deleteSubIssue: (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => Promise<void>;
};
export const SubIssuesRoot: FC<ISubIssuesRoot> = observer((props) => {
const { workspaceSlug, projectId, parentIssueId, disabled = false } = props;
// router
const pathname = usePathname();
const hashValue = useURLHash();
const {
issue: { getIssueById },
subIssues: { subIssuesByIssueId, stateDistributionByIssueId, subIssueHelpersByIssueId, setSubIssueHelpers },
fetchSubIssues,
createSubIssues,
updateSubIssue,
removeSubIssue,
deleteSubIssue,
isCreateIssueModalOpen,
toggleCreateIssueModal,
isSubIssuesModalOpen,
toggleSubIssuesModal,
toggleDeleteIssueModal,
} = useIssueDetail();
const { setTrackElement, captureIssueEvent } = useEventTracker();
// state
type TIssueCrudState = { toggle: boolean; parentIssueId: string | undefined; issue: TIssue | undefined };
const [issueCrudState, setIssueCrudState] = useState<{
create: TIssueCrudState;
existing: TIssueCrudState;
update: TIssueCrudState;
delete: TIssueCrudState;
}>({
create: {
toggle: false,
parentIssueId: undefined,
issue: undefined,
},
existing: {
toggle: false,
parentIssueId: undefined,
issue: undefined,
},
update: {
toggle: false,
parentIssueId: undefined,
issue: undefined,
},
delete: {
toggle: false,
parentIssueId: undefined,
issue: undefined,
},
});
const scrollToSubIssuesView = useCallback(() => {
if (hashValue === "sub-issues") {
setTimeout(() => {
const subIssueDiv = document.getElementById(`sub-issues`);
if (subIssueDiv)
subIssueDiv.scrollIntoView({
behavior: "smooth",
block: "start",
});
}, 200);
}
}, [hashValue]);
useEffect(() => {
if (hashValue) {
scrollToSubIssuesView();
}
}, [hashValue, scrollToSubIssuesView]);
const handleIssueCrudState = (
key: "create" | "existing" | "update" | "delete",
_parentIssueId: string | null,
issue: TIssue | null = null
) => {
setIssueCrudState({
...issueCrudState,
[key]: {
toggle: !issueCrudState[key].toggle,
parentIssueId: _parentIssueId,
issue: issue,
},
});
};
const subIssueOperations: TSubIssueOperations = useMemo(
() => ({
copyText: (text: string) => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${text}`).then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});
});
},
fetchSubIssues: async (workspaceSlug: string, projectId: string, parentIssueId: string) => {
try {
await fetchSubIssues(workspaceSlug, projectId, parentIssueId);
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error fetching sub-issues",
});
}
},
addSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueIds: string[]) => {
try {
await createSubIssues(workspaceSlug, projectId, parentIssueId, issueIds);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Sub-issues added successfully",
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error adding sub-issue",
});
}
},
updateSubIssue: async (
workspaceSlug: string,
projectId: string,
parentIssueId: string,
issueId: string,
issueData: Partial<TIssue>,
oldIssue: Partial<TIssue> = {},
fromModal: boolean = false
) => {
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, issueData, oldIssue, fromModal);
captureIssueEvent({
eventName: "Sub-issue updated",
payload: { ...oldIssue, ...issueData, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: Object.keys(issueData).join(","),
change_details: Object.values(issueData).join(","),
},
path: pathname,
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Sub-issue updated successfully",
});
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
} catch (error) {
captureIssueEvent({
eventName: "Sub-issue updated",
payload: { ...oldIssue, ...issueData, state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: Object.keys(issueData).join(","),
change_details: Object.values(issueData).join(","),
},
path: pathname,
});
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error updating sub-issue",
});
}
},
removeSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => {
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Sub-issue removed successfully",
});
captureIssueEvent({
eventName: "Sub-issue removed",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
updates: {
changed_property: "parent_id",
change_details: parentIssueId,
},
path: pathname,
});
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
} catch (error) {
captureIssueEvent({
eventName: "Sub-issue removed",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
updates: {
changed_property: "parent_id",
change_details: parentIssueId,
},
path: pathname,
});
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error removing sub-issue",
});
}
},
deleteSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => {
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
captureIssueEvent({
eventName: "Sub-issue deleted",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
path: pathname,
});
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
} catch (error) {
captureIssueEvent({
eventName: "Sub-issue removed",
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
path: pathname,
});
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error deleting issue",
});
}
},
}),
[fetchSubIssues, createSubIssues, updateSubIssue, removeSubIssue, deleteSubIssue, setSubIssueHelpers]
);
const issue = getIssueById(parentIssueId);
const subIssuesDistribution = stateDistributionByIssueId(parentIssueId);
const subIssues = subIssuesByIssueId(parentIssueId);
const subIssueHelpers = subIssueHelpersByIssueId(`${parentIssueId}_root`);
const handleFetchSubIssues = useCallback(async () => {
if (!subIssueHelpers.issue_visibility.includes(parentIssueId)) {
setSubIssueHelpers(`${parentIssueId}_root`, "preview_loader", parentIssueId);
await subIssueOperations.fetchSubIssues(workspaceSlug, projectId, parentIssueId);
setSubIssueHelpers(`${parentIssueId}_root`, "preview_loader", parentIssueId);
}
setSubIssueHelpers(`${parentIssueId}_root`, "issue_visibility", parentIssueId);
}, [
parentIssueId,
projectId,
setSubIssueHelpers,
subIssueHelpers.issue_visibility,
subIssueOperations,
workspaceSlug,
]);
useEffect(() => {
handleFetchSubIssues();
return () => {
handleFetchSubIssues();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [parentIssueId]);
if (!issue) return <></>;
return (
<div id="sub-issues" className="h-full w-full space-y-2">
{!subIssues ? (
<div className="py-3 text-center text-sm font-medium text-custom-text-300">Loading...</div>
) : (
<>
{subIssues && subIssues?.length > 0 ? (
<>
<div className="relative flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<button
type="button"
className="flex items-center gap-1 rounded py-1 px-2 transition-all hover:bg-custom-background-80 font-medium"
onClick={handleFetchSubIssues}
>
<div className="flex flex-shrink-0 items-center justify-center">
{subIssueHelpers.preview_loader.includes(parentIssueId) ? (
<Loader strokeWidth={2} className="h-3 w-3 animate-spin" />
) : (
<ChevronRight
className={cn("h-3 w-3 transition-all", {
"rotate-90": subIssueHelpers.issue_visibility.includes(parentIssueId),
})}
strokeWidth={2}
/>
)}
</div>
<div>Sub-issues</div>
</button>
<div className="flex items-center gap-2 text-custom-text-300">
<CircularProgressIndicator
size={16}
percentage={
subIssuesDistribution?.completed?.length && subIssues.length
? (subIssuesDistribution?.completed?.length / subIssues.length) * 100
: 0
}
strokeWidth={3}
/>
<span>
{subIssuesDistribution?.completed?.length ?? 0}/{subIssues.length} Done
</span>
</div>
</div>
{!disabled && (
<CustomMenu
label={
<>
<Plus className="h-3 w-3" />
Add sub-issue
</>
}
buttonClassName="whitespace-nowrap"
placement="bottom-end"
noBorder
noChevron
>
<CustomMenu.MenuItem
onClick={() => {
setTrackElement("Issue detail nested sub-issue");
handleIssueCrudState("create", parentIssueId, null);
toggleCreateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Pencil className="h-3 w-3" />
<span>Create new</span>
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={() => {
setTrackElement("Issue detail nested sub-issue");
handleIssueCrudState("existing", parentIssueId, null);
toggleSubIssuesModal(issue.id);
}}
>
<div className="flex items-center gap-2">
<LayersIcon className="h-3 w-3" />
<span>Add existing</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
)}
</div>
{subIssueHelpers.issue_visibility.includes(parentIssueId) && (
<div className="border border-b-0 border-custom-border-100">
<IssueList
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssueId={parentIssueId}
rootIssueId={parentIssueId}
spacingLeft={10}
disabled={!disabled}
handleIssueCrudState={handleIssueCrudState}
subIssueOperations={subIssueOperations}
/>
</div>
)}
</>
) : (
!disabled && (
<div className="flex items-center justify-between">
<div className="text-xs italic text-custom-text-300">No sub-issues yet</div>
<CustomMenu
label={
<>
<Plus className="h-3 w-3" />
Add sub-issue
</>
}
buttonClassName="whitespace-nowrap"
placement="bottom-end"
noBorder
noChevron
>
<CustomMenu.MenuItem
onClick={() => {
setTrackElement("Issue detail nested sub-issue");
handleIssueCrudState("create", parentIssueId, null);
toggleCreateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Pencil className="h-3 w-3" />
<span>Create new</span>
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={() => {
setTrackElement("Issue detail nested sub-issue");
handleIssueCrudState("existing", parentIssueId, null);
toggleSubIssuesModal(issue.id);
}}
>
<div className="flex items-center gap-2">
<LayersIcon className="h-3 w-3" />
<span>Add existing</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</div>
)
)}
{/* issue create, add from existing , update and delete modals */}
{issueCrudState?.create?.toggle && issueCrudState?.create?.parentIssueId && isCreateIssueModalOpen && (
<CreateUpdateIssueModal
isOpen={issueCrudState?.create?.toggle}
data={{
parent_id: issueCrudState?.create?.parentIssueId,
}}
onClose={() => {
handleIssueCrudState("create", null, null);
toggleCreateIssueModal(false);
}}
onSubmit={async (_issue: TIssue) => {
if (_issue.parent_id) {
await subIssueOperations.addSubIssue(workspaceSlug, projectId, parentIssueId, [_issue.id]);
}
}}
/>
)}
{issueCrudState?.existing?.toggle && issueCrudState?.existing?.parentIssueId && isSubIssuesModalOpen && (
<ExistingIssuesListModal
workspaceSlug={workspaceSlug}
projectId={projectId}
isOpen={issueCrudState?.existing?.toggle}
handleClose={() => {
handleIssueCrudState("existing", null, null);
toggleSubIssuesModal(null);
}}
searchParams={{ sub_issue: true, issue_id: issueCrudState?.existing?.parentIssueId }}
handleOnSubmit={(_issue) =>
subIssueOperations.addSubIssue(
workspaceSlug,
projectId,
parentIssueId,
_issue.map((issue) => issue.id)
)
}
workspaceLevelToggle
/>
)}
{issueCrudState?.update?.toggle && issueCrudState?.update?.issue && (
<>
<CreateUpdateIssueModal
isOpen={issueCrudState?.update?.toggle}
onClose={() => {
handleIssueCrudState("update", null, null);
toggleCreateIssueModal(false);
}}
data={issueCrudState?.update?.issue ?? undefined}
onSubmit={async (_issue: TIssue) => {
await subIssueOperations.updateSubIssue(
workspaceSlug,
projectId,
parentIssueId,
_issue.id,
_issue,
issueCrudState?.update?.issue,
true
);
}}
/>
</>
)}
{issueCrudState?.delete?.toggle &&
issueCrudState?.delete?.issue &&
issueCrudState.delete.parentIssueId &&
issueCrudState.delete.issue.id && (
<DeleteIssueModal
isOpen={issueCrudState?.delete?.toggle}
handleClose={() => {
handleIssueCrudState("delete", null, null);
toggleDeleteIssueModal(null);
}}
data={issueCrudState?.delete?.issue as TIssue}
onSubmit={async () =>
await subIssueOperations.deleteSubIssue(
workspaceSlug,
projectId,
issueCrudState?.delete?.parentIssueId as string,
issueCrudState?.delete?.issue?.id as string
)
}
isSubIssue
/>
)}
</>
)}
</div>
);
});