forked from github/plane
fix: edit/delete for draft issue (#2190)
* fix: edit/delete * fix: build issue * fix: draft issue modal opening in kanban card
This commit is contained in:
parent
eda4da8aed
commit
32d945be0d
@ -52,10 +52,22 @@ const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const issueViewForDraftIssues: { type: TIssueViewOptions; Icon: any }[] = [
|
||||
{
|
||||
type: "list",
|
||||
Icon: FormatListBulletedOutlined,
|
||||
},
|
||||
{
|
||||
type: "kanban",
|
||||
Icon: GridViewOutlined,
|
||||
},
|
||||
];
|
||||
|
||||
export const IssuesFilterView: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, viewId } = router.query;
|
||||
const isArchivedIssues = router.pathname.includes("archived-issues");
|
||||
const isDraftIssues = router.pathname.includes("draft-issues");
|
||||
|
||||
const {
|
||||
displayFilters,
|
||||
@ -75,7 +87,7 @@ export const IssuesFilterView: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{!isArchivedIssues && (
|
||||
{!isArchivedIssues && !isDraftIssues && (
|
||||
<div className="flex items-center gap-x-1">
|
||||
{issueViewOptions.map((option) => (
|
||||
<Tooltip
|
||||
@ -105,6 +117,36 @@ export const IssuesFilterView: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{isDraftIssues && (
|
||||
<div className="flex items-center gap-x-1">
|
||||
{issueViewForDraftIssues.map((option) => (
|
||||
<Tooltip
|
||||
key={option.type}
|
||||
tooltipContent={
|
||||
<span className="capitalize">{replaceUnderscoreIfSnakeCase(option.type)} View</span>
|
||||
}
|
||||
position="bottom"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
|
||||
displayFilters.layout === option.type
|
||||
? "bg-custom-sidebar-background-80"
|
||||
: "text-custom-sidebar-text-200"
|
||||
}`}
|
||||
onClick={() => setDisplayFilters({ layout: option.type })}
|
||||
>
|
||||
<option.Icon
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
}}
|
||||
className={option.type === "gantt_chart" ? "rotate-90" : ""}
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<SelectFilters
|
||||
filters={filters}
|
||||
onSelect={(option) => {
|
||||
|
@ -49,7 +49,8 @@ type Props = {
|
||||
};
|
||||
secondaryButton?: React.ReactNode;
|
||||
};
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit" | "updateDraft") => void;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
||||
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
|
||||
handleOnDragEnd: (result: DropResult) => Promise<void>;
|
||||
openIssuesListModal: (() => void) | null;
|
||||
removeIssue: ((bridgeId: string, issueId: string) => void) | null;
|
||||
@ -66,6 +67,7 @@ export const AllViews: React.FC<Props> = ({
|
||||
dragDisabled = false,
|
||||
emptyState,
|
||||
handleIssueAction,
|
||||
handleDraftIssueAction,
|
||||
handleOnDragEnd,
|
||||
openIssuesListModal,
|
||||
removeIssue,
|
||||
@ -132,6 +134,7 @@ export const AllViews: React.FC<Props> = ({
|
||||
states={states}
|
||||
addIssueToGroup={addIssueToGroup}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
removeIssue={removeIssue}
|
||||
myIssueProjectId={myIssueProjectId}
|
||||
@ -149,6 +152,7 @@ export const AllViews: React.FC<Props> = ({
|
||||
disableAddIssueOption={disableAddIssueOption}
|
||||
dragDisabled={dragDisabled}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
handleTrashBox={handleTrashBox}
|
||||
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
|
||||
myIssueProjectId={myIssueProjectId}
|
||||
|
@ -19,7 +19,8 @@ type Props = {
|
||||
disableUserActions: boolean;
|
||||
disableAddIssueOption?: boolean;
|
||||
dragDisabled: boolean;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit" | "updateDraft") => void;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
||||
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
|
||||
handleTrashBox: (isDragging: boolean) => void;
|
||||
openIssuesListModal?: (() => void) | null;
|
||||
removeIssue: ((bridgeId: string, issueId: string) => void) | null;
|
||||
@ -37,6 +38,7 @@ export const AllBoards: React.FC<Props> = ({
|
||||
disableAddIssueOption = false,
|
||||
dragDisabled,
|
||||
handleIssueAction,
|
||||
handleDraftIssueAction,
|
||||
handleTrashBox,
|
||||
openIssuesListModal,
|
||||
myIssueProjectId,
|
||||
@ -94,6 +96,7 @@ export const AllBoards: React.FC<Props> = ({
|
||||
dragDisabled={dragDisabled}
|
||||
groupTitle={singleGroup}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
handleTrashBox={handleTrashBox}
|
||||
openIssuesListModal={openIssuesListModal ?? null}
|
||||
handleMyIssueOpen={handleMyIssueOpen}
|
||||
|
@ -24,6 +24,7 @@ type Props = {
|
||||
dragDisabled: boolean;
|
||||
groupTitle: string;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
||||
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
|
||||
handleTrashBox: (isDragging: boolean) => void;
|
||||
openIssuesListModal?: (() => void) | null;
|
||||
handleMyIssueOpen?: (issue: IIssue) => void;
|
||||
@ -41,6 +42,7 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
disableAddIssueOption = false,
|
||||
dragDisabled,
|
||||
handleIssueAction,
|
||||
handleDraftIssueAction,
|
||||
handleTrashBox,
|
||||
openIssuesListModal,
|
||||
handleMyIssueOpen,
|
||||
@ -136,6 +138,16 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
editIssue={() => handleIssueAction(issue, "edit")}
|
||||
makeIssueCopy={() => handleIssueAction(issue, "copy")}
|
||||
handleDeleteIssue={() => handleIssueAction(issue, "delete")}
|
||||
handleDraftIssueEdit={
|
||||
handleDraftIssueAction
|
||||
? () => handleDraftIssueAction(issue, "edit")
|
||||
: undefined
|
||||
}
|
||||
handleDraftIssueDelete={() =>
|
||||
handleDraftIssueAction
|
||||
? handleDraftIssueAction(issue, "delete")
|
||||
: undefined
|
||||
}
|
||||
handleTrashBox={handleTrashBox}
|
||||
handleMyIssueOpen={handleMyIssueOpen}
|
||||
removeIssue={() => {
|
||||
@ -155,7 +167,7 @@ export const SingleBoard: React.FC<Props> = ({
|
||||
display: displayFilters?.order_by === "sort_order" ? "inline" : "none",
|
||||
}}
|
||||
>
|
||||
{provided.placeholder}
|
||||
<>{provided.placeholder}</>
|
||||
</span>
|
||||
</div>
|
||||
{displayFilters?.group_by !== "created_by" && (
|
||||
|
@ -60,6 +60,8 @@ type Props = {
|
||||
handleMyIssueOpen?: (issue: IIssue) => void;
|
||||
removeIssue?: (() => void) | null;
|
||||
handleDeleteIssue: (issue: IIssue) => void;
|
||||
handleDraftIssueEdit?: () => void;
|
||||
handleDraftIssueDelete?: () => void;
|
||||
handleTrashBox: (isDragging: boolean) => void;
|
||||
disableUserActions: boolean;
|
||||
user: ICurrentUserResponse | undefined;
|
||||
@ -79,6 +81,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
removeIssue,
|
||||
groupTitle,
|
||||
handleDeleteIssue,
|
||||
handleDraftIssueEdit,
|
||||
handleDraftIssueDelete,
|
||||
handleTrashBox,
|
||||
disableUserActions,
|
||||
user,
|
||||
@ -99,6 +103,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const isDraftIssue = router.pathname.includes("draft-issues");
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const partialUpdateIssue = useCallback(
|
||||
@ -211,29 +217,47 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
>
|
||||
{!isNotAllowed && (
|
||||
<>
|
||||
<ContextMenu.Item Icon={PencilIcon} onClick={editIssue}>
|
||||
<ContextMenu.Item
|
||||
Icon={PencilIcon}
|
||||
onClick={() => {
|
||||
if (isDraftIssue && handleDraftIssueEdit) handleDraftIssueEdit();
|
||||
else editIssue();
|
||||
}}
|
||||
>
|
||||
Edit issue
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
|
||||
Make a copy...
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item Icon={TrashIcon} onClick={() => handleDeleteIssue(issue)}>
|
||||
{!isDraftIssue && (
|
||||
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
|
||||
Make a copy...
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
<ContextMenu.Item
|
||||
Icon={TrashIcon}
|
||||
onClick={() => {
|
||||
if (isDraftIssue && handleDraftIssueDelete) handleDraftIssueDelete();
|
||||
else handleDeleteIssue(issue);
|
||||
}}
|
||||
>
|
||||
Delete issue
|
||||
</ContextMenu.Item>
|
||||
</>
|
||||
)}
|
||||
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
|
||||
Copy issue link
|
||||
</ContextMenu.Item>
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
|
||||
Open issue in new tab
|
||||
{!isDraftIssue && (
|
||||
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
|
||||
Copy issue link
|
||||
</ContextMenu.Item>
|
||||
</a>
|
||||
)}
|
||||
{!isDraftIssue && (
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
|
||||
Open issue in new tab
|
||||
</ContextMenu.Item>
|
||||
</a>
|
||||
)}
|
||||
</ContextMenu>
|
||||
<div
|
||||
className={`mb-3 rounded bg-custom-background-100 shadow ${
|
||||
@ -268,13 +292,18 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<CustomMenu.MenuItem onClick={editIssue}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (isDraftIssue && handleDraftIssueEdit) handleDraftIssueEdit();
|
||||
else editIssue();
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span>Edit issue</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
{type !== "issue" && removeIssue && (
|
||||
{type !== "issue" && removeIssue && !isDraftIssue && (
|
||||
<CustomMenu.MenuItem onClick={removeIssue}>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<XMarkIcon className="h-4 w-4" />
|
||||
@ -282,18 +311,25 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (isDraftIssue && handleDraftIssueDelete) handleDraftIssueDelete();
|
||||
else handleDeleteIssue(issue);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete issue</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
<span>Copy issue Link</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
{!isDraftIssue && (
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
<span>Copy issue Link</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
@ -308,7 +344,10 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-left break-words line-clamp-2"
|
||||
onClick={openPeekOverview}
|
||||
onClick={() => {
|
||||
if (isDraftIssue && handleDraftIssueEdit) handleDraftIssueEdit();
|
||||
else openPeekOverview();
|
||||
}}
|
||||
>
|
||||
{issue.name}
|
||||
</button>
|
||||
|
@ -22,6 +22,7 @@ import { FiltersList, AllViews } from "components/core";
|
||||
import {
|
||||
CreateUpdateIssueModal,
|
||||
DeleteIssueModal,
|
||||
DeleteDraftIssueModal,
|
||||
IssuePeekOverview,
|
||||
CreateUpdateDraftIssueModal,
|
||||
} from "components/issues";
|
||||
@ -77,9 +78,11 @@ export const IssuesView: React.FC<Props> = ({
|
||||
|
||||
// selected draft issue
|
||||
const [selectedDraftIssue, setSelectedDraftIssue] = useState<IIssue | null>(null);
|
||||
const [selectedDraftForDelete, setSelectDraftForDelete] = useState<IIssue | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||
const isDraftIssues = router.asPath.includes("draft-issues");
|
||||
|
||||
const { user } = useUserAuth();
|
||||
|
||||
@ -114,7 +117,8 @@ export const IssuesView: React.FC<Props> = ({
|
||||
[setDeleteIssueModal, setIssueToDelete]
|
||||
);
|
||||
|
||||
const handleDraftIssueClick = (issue: any) => setSelectedDraftIssue(issue);
|
||||
const handleDraftIssueClick = useCallback((issue: any) => setSelectedDraftIssue(issue), []);
|
||||
const handleDraftIssueDelete = useCallback((issue: any) => setSelectDraftForDelete(issue), []);
|
||||
|
||||
const handleOnDragEnd = useCallback(
|
||||
async (result: DropResult) => {
|
||||
@ -345,15 +349,22 @@ export const IssuesView: React.FC<Props> = ({
|
||||
);
|
||||
|
||||
const handleIssueAction = useCallback(
|
||||
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
|
||||
(issue: IIssue, action: "copy" | "edit" | "delete") => {
|
||||
if (action === "copy") makeIssueCopy(issue);
|
||||
else if (action === "edit") handleEditIssue(issue);
|
||||
else if (action === "delete") handleDeleteIssue(issue);
|
||||
else if (action === "updateDraft") handleDraftIssueClick(issue);
|
||||
},
|
||||
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
|
||||
);
|
||||
|
||||
const handleDraftIssueAction = useCallback(
|
||||
(issue: IIssue, action: "edit" | "delete") => {
|
||||
if (action === "edit") handleDraftIssueClick(issue);
|
||||
else if (action === "delete") handleDraftIssueDelete(issue);
|
||||
},
|
||||
[handleDraftIssueClick, handleDraftIssueDelete]
|
||||
);
|
||||
|
||||
const removeIssueFromCycle = useCallback(
|
||||
(bridgeId: string, issueId: string) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
@ -494,6 +505,11 @@ export const IssuesView: React.FC<Props> = ({
|
||||
data={issueToDelete}
|
||||
user={user}
|
||||
/>
|
||||
<DeleteDraftIssueModal
|
||||
data={selectedDraftForDelete}
|
||||
isOpen={selectedDraftForDelete !== null}
|
||||
handleClose={() => setSelectDraftForDelete(null)}
|
||||
/>
|
||||
|
||||
{areFiltersApplied && (
|
||||
<>
|
||||
@ -550,23 +566,28 @@ export const IssuesView: React.FC<Props> = ({
|
||||
displayFilters.group_by === "assignees"
|
||||
}
|
||||
emptyState={{
|
||||
title: cycleId
|
||||
title: isDraftIssues
|
||||
? "Draft issues will appear here"
|
||||
: cycleId
|
||||
? "Cycle issues will appear here"
|
||||
: moduleId
|
||||
? "Module issues will appear here"
|
||||
: "Project issues will appear here",
|
||||
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.",
|
||||
primaryButton: {
|
||||
icon: <PlusIcon className="h-4 w-4" />,
|
||||
text: "New Issue",
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
},
|
||||
description: isDraftIssues
|
||||
? "Draft issues are issues that are not yet created."
|
||||
: "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.",
|
||||
primaryButton: !isDraftIssues
|
||||
? {
|
||||
icon: <PlusIcon className="h-4 w-4" />,
|
||||
text: "New Issue",
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
secondaryButton:
|
||||
cycleId || moduleId ? (
|
||||
<SecondaryButton
|
||||
@ -580,6 +601,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
}}
|
||||
handleOnDragEnd={handleOnDragEnd}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
openIssuesListModal={openIssuesListModal ?? null}
|
||||
removeIssue={cycleId ? removeIssueFromCycle : moduleId ? removeIssueFromModule : null}
|
||||
trashBox={trashBox}
|
||||
|
@ -14,7 +14,8 @@ import { ICurrentUserResponse, IIssue, IIssueViewProps, IState, UserAuth } from
|
||||
type Props = {
|
||||
states: IState[] | undefined;
|
||||
addIssueToGroup: (groupTitle: string) => void;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit" | "updateDraft") => void;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
||||
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
|
||||
openIssuesListModal?: (() => void) | null;
|
||||
myIssueProjectId?: string | null;
|
||||
handleMyIssueOpen?: (issue: IIssue) => void;
|
||||
@ -36,6 +37,7 @@ export const AllLists: React.FC<Props> = ({
|
||||
myIssueProjectId,
|
||||
removeIssue,
|
||||
states,
|
||||
handleDraftIssueAction,
|
||||
user,
|
||||
userAuth,
|
||||
viewProps,
|
||||
@ -82,6 +84,7 @@ export const AllLists: React.FC<Props> = ({
|
||||
groupTitle={singleGroup}
|
||||
currentState={currentState}
|
||||
addIssueToGroup={() => addIssueToGroup(singleGroup)}
|
||||
handleDraftIssueAction={handleDraftIssueAction}
|
||||
handleIssueAction={handleIssueAction}
|
||||
handleMyIssueOpen={handleMyIssueOpen}
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { mutate } from "swr";
|
||||
@ -18,6 +17,7 @@ import {
|
||||
ViewPrioritySelect,
|
||||
ViewStartDateSelect,
|
||||
ViewStateSelect,
|
||||
CreateUpdateDraftIssueModal,
|
||||
} from "components/issues";
|
||||
// ui
|
||||
import { Tooltip, CustomMenu, ContextMenu } from "components/ui";
|
||||
@ -62,6 +62,7 @@ type Props = {
|
||||
removeIssue?: (() => void) | null;
|
||||
handleDeleteIssue: (issue: IIssue) => void;
|
||||
handleDraftIssueSelect?: (issue: IIssue) => void;
|
||||
handleDraftIssueDelete?: (issue: IIssue) => void;
|
||||
handleMyIssueOpen?: (issue: IIssue) => void;
|
||||
disableUserActions: boolean;
|
||||
user: ICurrentUserResponse | undefined;
|
||||
@ -77,6 +78,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
makeIssueCopy,
|
||||
removeIssue,
|
||||
groupTitle,
|
||||
handleDraftIssueDelete,
|
||||
handleDeleteIssue,
|
||||
handleMyIssueOpen,
|
||||
disableUserActions,
|
||||
@ -208,26 +210,45 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
>
|
||||
{!isNotAllowed && (
|
||||
<>
|
||||
<ContextMenu.Item Icon={PencilIcon} onClick={editIssue}>
|
||||
<ContextMenu.Item
|
||||
Icon={PencilIcon}
|
||||
onClick={() => {
|
||||
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
|
||||
else editIssue();
|
||||
}}
|
||||
>
|
||||
Edit issue
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
|
||||
Make a copy...
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item Icon={TrashIcon} onClick={() => handleDeleteIssue(issue)}>
|
||||
{!isDraftIssues && (
|
||||
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
|
||||
Make a copy...
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
<ContextMenu.Item
|
||||
Icon={TrashIcon}
|
||||
onClick={() => {
|
||||
if (isDraftIssues && handleDraftIssueDelete) handleDraftIssueDelete(issue);
|
||||
else handleDeleteIssue(issue);
|
||||
}}
|
||||
>
|
||||
Delete issue
|
||||
</ContextMenu.Item>
|
||||
</>
|
||||
)}
|
||||
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
|
||||
Copy issue link
|
||||
</ContextMenu.Item>
|
||||
<a href={issuePath} target="_blank" rel="noreferrer noopener">
|
||||
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
|
||||
Open issue in new tab
|
||||
</ContextMenu.Item>
|
||||
</a>
|
||||
{!isDraftIssues && (
|
||||
<>
|
||||
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
|
||||
Copy issue link
|
||||
</ContextMenu.Item>
|
||||
<a href={issuePath} target="_blank" rel="noreferrer noopener">
|
||||
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
|
||||
Open issue in new tab
|
||||
</ContextMenu.Item>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</ContextMenu>
|
||||
|
||||
<div
|
||||
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
|
||||
onContextMenu={(e) => {
|
||||
@ -254,8 +275,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
className="truncate text-[0.825rem] text-custom-text-100"
|
||||
onClick={() => {
|
||||
if (!isDraftIssues) openPeekOverview(issue);
|
||||
|
||||
if (handleDraftIssueSelect) handleDraftIssueSelect(issue);
|
||||
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
|
||||
}}
|
||||
>
|
||||
{issue.name}
|
||||
@ -354,7 +374,12 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
)}
|
||||
{type && !isNotAllowed && (
|
||||
<CustomMenu width="auto" ellipsis>
|
||||
<CustomMenu.MenuItem onClick={editIssue}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
|
||||
else editIssue();
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span>Edit issue</span>
|
||||
@ -368,18 +393,25 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (isDraftIssues && handleDraftIssueDelete) handleDraftIssueDelete(issue);
|
||||
else handleDeleteIssue(issue);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete issue</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
<span>Copy issue link</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
{!isDraftIssues && (
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
<span>Copy issue link</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
|
@ -39,7 +39,8 @@ type Props = {
|
||||
currentState?: IState | null;
|
||||
groupTitle: string;
|
||||
addIssueToGroup: () => void;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit" | "updateDraft") => void;
|
||||
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
|
||||
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
|
||||
openIssuesListModal?: (() => void) | null;
|
||||
handleMyIssueOpen?: (issue: IIssue) => void;
|
||||
removeIssue: ((bridgeId: string, issueId: string) => void) | null;
|
||||
@ -56,6 +57,7 @@ export const SingleList: React.FC<Props> = ({
|
||||
addIssueToGroup,
|
||||
handleIssueAction,
|
||||
openIssuesListModal,
|
||||
handleDraftIssueAction,
|
||||
handleMyIssueOpen,
|
||||
removeIssue,
|
||||
disableUserActions,
|
||||
@ -253,7 +255,16 @@ export const SingleList: React.FC<Props> = ({
|
||||
editIssue={() => handleIssueAction(issue, "edit")}
|
||||
makeIssueCopy={() => handleIssueAction(issue, "copy")}
|
||||
handleDeleteIssue={() => handleIssueAction(issue, "delete")}
|
||||
handleDraftIssueSelect={() => handleIssueAction(issue, "updateDraft")}
|
||||
handleDraftIssueSelect={
|
||||
handleDraftIssueAction
|
||||
? () => handleDraftIssueAction(issue, "edit")
|
||||
: undefined
|
||||
}
|
||||
handleDraftIssueDelete={
|
||||
handleDraftIssueAction
|
||||
? () => handleDraftIssueAction(issue, "delete")
|
||||
: undefined
|
||||
}
|
||||
handleMyIssueOpen={handleMyIssueOpen}
|
||||
removeIssue={() => {
|
||||
if (removeIssue !== null && issue.bridge_id)
|
||||
|
145
web/components/issues/delete-draft-issue-modal.tsx
Normal file
145
web/components/issues/delete-draft-issue-modal.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { mutate } from "swr";
|
||||
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import issueServices from "services/issues.service";
|
||||
// hooks
|
||||
import useIssuesView from "hooks/use-issues-view";
|
||||
import useToast from "hooks/use-toast";
|
||||
// icons
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
// ui
|
||||
import { SecondaryButton, DangerButton } from "components/ui";
|
||||
// types
|
||||
import type { IIssue, ICurrentUserResponse } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
data: IIssue | null;
|
||||
user?: ICurrentUserResponse;
|
||||
onSubmit?: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
||||
const { isOpen, handleClose, data, user, onSubmit } = props;
|
||||
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { params } = useIssuesView();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
setIsDeleteLoading(false);
|
||||
}, [isOpen]);
|
||||
|
||||
const onClose = () => {
|
||||
setIsDeleteLoading(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleDeletion = async () => {
|
||||
if (!workspaceSlug || !data) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await issueServices
|
||||
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
||||
.then(() => {
|
||||
setIsDeleteLoading(false);
|
||||
handleClose();
|
||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
message: "Draft Issue deleted successfully",
|
||||
type: "success",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
handleClose();
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
message: "Something went wrong",
|
||||
type: "error",
|
||||
});
|
||||
setIsDeleteLoading(false);
|
||||
});
|
||||
if (onSubmit) await onSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
<div className="flex w-full items-center justify-start gap-6">
|
||||
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
||||
<ExclamationTriangleIcon
|
||||
className="h-6 w-6 text-red-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<span className="flex items-center justify-start">
|
||||
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Draft Issue</h3>
|
||||
</span>
|
||||
</div>
|
||||
<span>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete issue{" "}
|
||||
<span className="break-words font-medium text-custom-text-100">
|
||||
{data?.project_detail.identifier}-{data?.sequence_id}
|
||||
</span>
|
||||
{""}? All of the data related to the draft issue will be permanently removed.
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</span>
|
||||
<div className="flex justify-end gap-2">
|
||||
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
|
||||
<DangerButton onClick={handleDeletion} loading={isDeleteLoading}>
|
||||
{isDeleteLoading ? "Deleting..." : "Delete Issue"}
|
||||
</DangerButton>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
@ -133,9 +133,15 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
|
||||
|
||||
const issueName = watch("name");
|
||||
|
||||
const payload = {
|
||||
const payload: Partial<IIssue> = {
|
||||
name: getValues("name"),
|
||||
description: getValues("description"),
|
||||
state: getValues("state"),
|
||||
priority: getValues("priority"),
|
||||
assignees: getValues("assignees"),
|
||||
target_date: getValues("target_date"),
|
||||
labels: getValues("labels"),
|
||||
project: getValues("project"),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -17,5 +17,8 @@ export * from "./label";
|
||||
export * from "./issue-reaction";
|
||||
export * from "./peek-overview";
|
||||
export * from "./confirm-issue-discard";
|
||||
|
||||
// draft issue
|
||||
export * from "./draft-issue-form";
|
||||
export * from "./draft-issue-modal";
|
||||
export * from "./delete-draft-issue-modal";
|
||||
|
@ -38,6 +38,9 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
"priority",
|
||||
"dueDate",
|
||||
"priority",
|
||||
"state",
|
||||
"startDate",
|
||||
"project",
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -47,7 +50,7 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-between w-full rounded cursor-pointer px-4 gap-1 ${
|
||||
className={`flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 ${
|
||||
store?.theme?.sidebarCollapsed
|
||||
? "px-2 hover:bg-custom-sidebar-background-80"
|
||||
: "px-3 shadow border-[0.5px] border-custom-border-300"
|
||||
@ -80,7 +83,7 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
<div>
|
||||
<Menu.Button
|
||||
type="button"
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 p-2 ${
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 p-1.5 ${
|
||||
open ? "rotate-180 pl-0" : "rotate-0 pr-0"
|
||||
}`}
|
||||
>
|
||||
@ -108,7 +111,7 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
>
|
||||
<PenSquare
|
||||
size={16}
|
||||
className="!text-lg !leading-4 text-custom-sidebar-text-300 mx-2"
|
||||
className="!text-lg !leading-4 text-custom-sidebar-text-300 mr-2"
|
||||
/>
|
||||
Last Drafted Issue
|
||||
</button>
|
||||
|
@ -55,7 +55,7 @@ const ProjectDraftIssues: NextPage = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/`)}
|
||||
className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs"
|
||||
className="flex items-center gap-1.5 rounded border border-custom-border-200 px-3 py-1.5 text-xs"
|
||||
>
|
||||
<PenSquare className="h-3 w-3 text-custom-text-300" />
|
||||
<span>Draft Issues</span>
|
||||
|
Loading…
Reference in New Issue
Block a user