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:
Dakshesh Jain 2023-09-15 12:51:10 +05:30 committed by GitHub
parent eda4da8aed
commit 32d945be0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 403 additions and 78 deletions

View File

@ -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 = () => { export const IssuesFilterView: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query; const { workspaceSlug, projectId, viewId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const isDraftIssues = router.pathname.includes("draft-issues");
const { const {
displayFilters, displayFilters,
@ -75,7 +87,7 @@ export const IssuesFilterView: React.FC = () => {
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!isArchivedIssues && ( {!isArchivedIssues && !isDraftIssues && (
<div className="flex items-center gap-x-1"> <div className="flex items-center gap-x-1">
{issueViewOptions.map((option) => ( {issueViewOptions.map((option) => (
<Tooltip <Tooltip
@ -105,6 +117,36 @@ export const IssuesFilterView: React.FC = () => {
))} ))}
</div> </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 <SelectFilters
filters={filters} filters={filters}
onSelect={(option) => { onSelect={(option) => {

View File

@ -49,7 +49,8 @@ type Props = {
}; };
secondaryButton?: React.ReactNode; 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>; handleOnDragEnd: (result: DropResult) => Promise<void>;
openIssuesListModal: (() => void) | null; openIssuesListModal: (() => void) | null;
removeIssue: ((bridgeId: string, issueId: string) => void) | null; removeIssue: ((bridgeId: string, issueId: string) => void) | null;
@ -66,6 +67,7 @@ export const AllViews: React.FC<Props> = ({
dragDisabled = false, dragDisabled = false,
emptyState, emptyState,
handleIssueAction, handleIssueAction,
handleDraftIssueAction,
handleOnDragEnd, handleOnDragEnd,
openIssuesListModal, openIssuesListModal,
removeIssue, removeIssue,
@ -132,6 +134,7 @@ export const AllViews: React.FC<Props> = ({
states={states} states={states}
addIssueToGroup={addIssueToGroup} addIssueToGroup={addIssueToGroup}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
handleDraftIssueAction={handleDraftIssueAction}
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null} openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
removeIssue={removeIssue} removeIssue={removeIssue}
myIssueProjectId={myIssueProjectId} myIssueProjectId={myIssueProjectId}
@ -149,6 +152,7 @@ export const AllViews: React.FC<Props> = ({
disableAddIssueOption={disableAddIssueOption} disableAddIssueOption={disableAddIssueOption}
dragDisabled={dragDisabled} dragDisabled={dragDisabled}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
handleDraftIssueAction={handleDraftIssueAction}
handleTrashBox={handleTrashBox} handleTrashBox={handleTrashBox}
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null} openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
myIssueProjectId={myIssueProjectId} myIssueProjectId={myIssueProjectId}

View File

@ -19,7 +19,8 @@ type Props = {
disableUserActions: boolean; disableUserActions: boolean;
disableAddIssueOption?: boolean; disableAddIssueOption?: boolean;
dragDisabled: 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; handleTrashBox: (isDragging: boolean) => void;
openIssuesListModal?: (() => void) | null; openIssuesListModal?: (() => void) | null;
removeIssue: ((bridgeId: string, issueId: string) => void) | null; removeIssue: ((bridgeId: string, issueId: string) => void) | null;
@ -37,6 +38,7 @@ export const AllBoards: React.FC<Props> = ({
disableAddIssueOption = false, disableAddIssueOption = false,
dragDisabled, dragDisabled,
handleIssueAction, handleIssueAction,
handleDraftIssueAction,
handleTrashBox, handleTrashBox,
openIssuesListModal, openIssuesListModal,
myIssueProjectId, myIssueProjectId,
@ -94,6 +96,7 @@ export const AllBoards: React.FC<Props> = ({
dragDisabled={dragDisabled} dragDisabled={dragDisabled}
groupTitle={singleGroup} groupTitle={singleGroup}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
handleDraftIssueAction={handleDraftIssueAction}
handleTrashBox={handleTrashBox} handleTrashBox={handleTrashBox}
openIssuesListModal={openIssuesListModal ?? null} openIssuesListModal={openIssuesListModal ?? null}
handleMyIssueOpen={handleMyIssueOpen} handleMyIssueOpen={handleMyIssueOpen}

View File

@ -24,6 +24,7 @@ type Props = {
dragDisabled: boolean; dragDisabled: boolean;
groupTitle: string; groupTitle: string;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
handleTrashBox: (isDragging: boolean) => void; handleTrashBox: (isDragging: boolean) => void;
openIssuesListModal?: (() => void) | null; openIssuesListModal?: (() => void) | null;
handleMyIssueOpen?: (issue: IIssue) => void; handleMyIssueOpen?: (issue: IIssue) => void;
@ -41,6 +42,7 @@ export const SingleBoard: React.FC<Props> = ({
disableAddIssueOption = false, disableAddIssueOption = false,
dragDisabled, dragDisabled,
handleIssueAction, handleIssueAction,
handleDraftIssueAction,
handleTrashBox, handleTrashBox,
openIssuesListModal, openIssuesListModal,
handleMyIssueOpen, handleMyIssueOpen,
@ -136,6 +138,16 @@ export const SingleBoard: React.FC<Props> = ({
editIssue={() => handleIssueAction(issue, "edit")} editIssue={() => handleIssueAction(issue, "edit")}
makeIssueCopy={() => handleIssueAction(issue, "copy")} makeIssueCopy={() => handleIssueAction(issue, "copy")}
handleDeleteIssue={() => handleIssueAction(issue, "delete")} handleDeleteIssue={() => handleIssueAction(issue, "delete")}
handleDraftIssueEdit={
handleDraftIssueAction
? () => handleDraftIssueAction(issue, "edit")
: undefined
}
handleDraftIssueDelete={() =>
handleDraftIssueAction
? handleDraftIssueAction(issue, "delete")
: undefined
}
handleTrashBox={handleTrashBox} handleTrashBox={handleTrashBox}
handleMyIssueOpen={handleMyIssueOpen} handleMyIssueOpen={handleMyIssueOpen}
removeIssue={() => { removeIssue={() => {
@ -155,7 +167,7 @@ export const SingleBoard: React.FC<Props> = ({
display: displayFilters?.order_by === "sort_order" ? "inline" : "none", display: displayFilters?.order_by === "sort_order" ? "inline" : "none",
}} }}
> >
{provided.placeholder} <>{provided.placeholder}</>
</span> </span>
</div> </div>
{displayFilters?.group_by !== "created_by" && ( {displayFilters?.group_by !== "created_by" && (

View File

@ -60,6 +60,8 @@ type Props = {
handleMyIssueOpen?: (issue: IIssue) => void; handleMyIssueOpen?: (issue: IIssue) => void;
removeIssue?: (() => void) | null; removeIssue?: (() => void) | null;
handleDeleteIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void;
handleDraftIssueEdit?: () => void;
handleDraftIssueDelete?: () => void;
handleTrashBox: (isDragging: boolean) => void; handleTrashBox: (isDragging: boolean) => void;
disableUserActions: boolean; disableUserActions: boolean;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
@ -79,6 +81,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
removeIssue, removeIssue,
groupTitle, groupTitle,
handleDeleteIssue, handleDeleteIssue,
handleDraftIssueEdit,
handleDraftIssueDelete,
handleTrashBox, handleTrashBox,
disableUserActions, disableUserActions,
user, user,
@ -99,6 +103,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const isDraftIssue = router.pathname.includes("draft-issues");
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const partialUpdateIssue = useCallback( const partialUpdateIssue = useCallback(
@ -211,29 +217,47 @@ export const SingleBoardIssue: React.FC<Props> = ({
> >
{!isNotAllowed && ( {!isNotAllowed && (
<> <>
<ContextMenu.Item Icon={PencilIcon} onClick={editIssue}> <ContextMenu.Item
Icon={PencilIcon}
onClick={() => {
if (isDraftIssue && handleDraftIssueEdit) handleDraftIssueEdit();
else editIssue();
}}
>
Edit issue Edit issue
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}> {!isDraftIssue && (
Make a copy... <ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
</ContextMenu.Item> Make a copy...
<ContextMenu.Item Icon={TrashIcon} onClick={() => handleDeleteIssue(issue)}> </ContextMenu.Item>
)}
<ContextMenu.Item
Icon={TrashIcon}
onClick={() => {
if (isDraftIssue && handleDraftIssueDelete) handleDraftIssueDelete();
else handleDeleteIssue(issue);
}}
>
Delete issue Delete issue
</ContextMenu.Item> </ContextMenu.Item>
</> </>
)} )}
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}> {!isDraftIssue && (
Copy issue link <ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
</ContextMenu.Item> Copy issue link
<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> </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> </ContextMenu>
<div <div
className={`mb-3 rounded bg-custom-background-100 shadow ${ className={`mb-3 rounded bg-custom-background-100 shadow ${
@ -268,13 +292,18 @@ export const SingleBoardIssue: React.FC<Props> = ({
</button> </button>
} }
> >
<CustomMenu.MenuItem onClick={editIssue}> <CustomMenu.MenuItem
onClick={() => {
if (isDraftIssue && handleDraftIssueEdit) handleDraftIssueEdit();
else editIssue();
}}
>
<div className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<PencilIcon className="h-4 w-4" /> <PencilIcon className="h-4 w-4" />
<span>Edit issue</span> <span>Edit issue</span>
</div> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
{type !== "issue" && removeIssue && ( {type !== "issue" && removeIssue && !isDraftIssue && (
<CustomMenu.MenuItem onClick={removeIssue}> <CustomMenu.MenuItem onClick={removeIssue}>
<div className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<XMarkIcon className="h-4 w-4" /> <XMarkIcon className="h-4 w-4" />
@ -282,18 +311,25 @@ export const SingleBoardIssue: React.FC<Props> = ({
</div> </div>
</CustomMenu.MenuItem> </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"> <div className="flex items-center justify-start gap-2">
<TrashIcon className="h-4 w-4" /> <TrashIcon className="h-4 w-4" />
<span>Delete issue</span> <span>Delete issue</span>
</div> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}> {!isDraftIssue && (
<div className="flex items-center justify-start gap-2"> <CustomMenu.MenuItem onClick={handleCopyText}>
<LinkIcon className="h-4 w-4" /> <div className="flex items-center justify-start gap-2">
<span>Copy issue Link</span> <LinkIcon className="h-4 w-4" />
</div> <span>Copy issue Link</span>
</CustomMenu.MenuItem> </div>
</CustomMenu.MenuItem>
)}
</CustomMenu> </CustomMenu>
)} )}
</div> </div>
@ -308,7 +344,10 @@ export const SingleBoardIssue: React.FC<Props> = ({
<button <button
type="button" type="button"
className="text-sm text-left break-words line-clamp-2" className="text-sm text-left break-words line-clamp-2"
onClick={openPeekOverview} onClick={() => {
if (isDraftIssue && handleDraftIssueEdit) handleDraftIssueEdit();
else openPeekOverview();
}}
> >
{issue.name} {issue.name}
</button> </button>

View File

@ -22,6 +22,7 @@ import { FiltersList, AllViews } from "components/core";
import { import {
CreateUpdateIssueModal, CreateUpdateIssueModal,
DeleteIssueModal, DeleteIssueModal,
DeleteDraftIssueModal,
IssuePeekOverview, IssuePeekOverview,
CreateUpdateDraftIssueModal, CreateUpdateDraftIssueModal,
} from "components/issues"; } from "components/issues";
@ -77,9 +78,11 @@ export const IssuesView: React.FC<Props> = ({
// selected draft issue // selected draft issue
const [selectedDraftIssue, setSelectedDraftIssue] = useState<IIssue | null>(null); const [selectedDraftIssue, setSelectedDraftIssue] = useState<IIssue | null>(null);
const [selectedDraftForDelete, setSelectDraftForDelete] = useState<IIssue | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const isDraftIssues = router.asPath.includes("draft-issues");
const { user } = useUserAuth(); const { user } = useUserAuth();
@ -114,7 +117,8 @@ export const IssuesView: React.FC<Props> = ({
[setDeleteIssueModal, setIssueToDelete] [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( const handleOnDragEnd = useCallback(
async (result: DropResult) => { async (result: DropResult) => {
@ -345,15 +349,22 @@ export const IssuesView: React.FC<Props> = ({
); );
const handleIssueAction = useCallback( const handleIssueAction = useCallback(
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => { (issue: IIssue, action: "copy" | "edit" | "delete") => {
if (action === "copy") makeIssueCopy(issue); if (action === "copy") makeIssueCopy(issue);
else if (action === "edit") handleEditIssue(issue); else if (action === "edit") handleEditIssue(issue);
else if (action === "delete") handleDeleteIssue(issue); else if (action === "delete") handleDeleteIssue(issue);
else if (action === "updateDraft") handleDraftIssueClick(issue);
}, },
[makeIssueCopy, handleEditIssue, handleDeleteIssue] [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( const removeIssueFromCycle = useCallback(
(bridgeId: string, issueId: string) => { (bridgeId: string, issueId: string) => {
if (!workspaceSlug || !projectId || !cycleId) return; if (!workspaceSlug || !projectId || !cycleId) return;
@ -494,6 +505,11 @@ export const IssuesView: React.FC<Props> = ({
data={issueToDelete} data={issueToDelete}
user={user} user={user}
/> />
<DeleteDraftIssueModal
data={selectedDraftForDelete}
isOpen={selectedDraftForDelete !== null}
handleClose={() => setSelectDraftForDelete(null)}
/>
{areFiltersApplied && ( {areFiltersApplied && (
<> <>
@ -550,23 +566,28 @@ export const IssuesView: React.FC<Props> = ({
displayFilters.group_by === "assignees" displayFilters.group_by === "assignees"
} }
emptyState={{ emptyState={{
title: cycleId title: isDraftIssues
? "Draft issues will appear here"
: cycleId
? "Cycle issues will appear here" ? "Cycle issues will appear here"
: moduleId : moduleId
? "Module issues will appear here" ? "Module issues will appear here"
: "Project issues will appear here", : "Project issues will appear here",
description: description: isDraftIssues
"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.", ? "Draft issues are issues that are not yet created."
primaryButton: { : "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.",
icon: <PlusIcon className="h-4 w-4" />, primaryButton: !isDraftIssues
text: "New Issue", ? {
onClick: () => { icon: <PlusIcon className="h-4 w-4" />,
const e = new KeyboardEvent("keydown", { text: "New Issue",
key: "c", onClick: () => {
}); const e = new KeyboardEvent("keydown", {
document.dispatchEvent(e); key: "c",
}, });
}, document.dispatchEvent(e);
},
}
: undefined,
secondaryButton: secondaryButton:
cycleId || moduleId ? ( cycleId || moduleId ? (
<SecondaryButton <SecondaryButton
@ -580,6 +601,7 @@ export const IssuesView: React.FC<Props> = ({
}} }}
handleOnDragEnd={handleOnDragEnd} handleOnDragEnd={handleOnDragEnd}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
handleDraftIssueAction={handleDraftIssueAction}
openIssuesListModal={openIssuesListModal ?? null} openIssuesListModal={openIssuesListModal ?? null}
removeIssue={cycleId ? removeIssueFromCycle : moduleId ? removeIssueFromModule : null} removeIssue={cycleId ? removeIssueFromCycle : moduleId ? removeIssueFromModule : null}
trashBox={trashBox} trashBox={trashBox}

View File

@ -14,7 +14,8 @@ import { ICurrentUserResponse, IIssue, IIssueViewProps, IState, UserAuth } from
type Props = { type Props = {
states: IState[] | undefined; states: IState[] | undefined;
addIssueToGroup: (groupTitle: string) => void; 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; openIssuesListModal?: (() => void) | null;
myIssueProjectId?: string | null; myIssueProjectId?: string | null;
handleMyIssueOpen?: (issue: IIssue) => void; handleMyIssueOpen?: (issue: IIssue) => void;
@ -36,6 +37,7 @@ export const AllLists: React.FC<Props> = ({
myIssueProjectId, myIssueProjectId,
removeIssue, removeIssue,
states, states,
handleDraftIssueAction,
user, user,
userAuth, userAuth,
viewProps, viewProps,
@ -82,6 +84,7 @@ export const AllLists: React.FC<Props> = ({
groupTitle={singleGroup} groupTitle={singleGroup}
currentState={currentState} currentState={currentState}
addIssueToGroup={() => addIssueToGroup(singleGroup)} addIssueToGroup={() => addIssueToGroup(singleGroup)}
handleDraftIssueAction={handleDraftIssueAction}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
handleMyIssueOpen={handleMyIssueOpen} handleMyIssueOpen={handleMyIssueOpen}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}

View File

@ -1,6 +1,5 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
@ -18,6 +17,7 @@ import {
ViewPrioritySelect, ViewPrioritySelect,
ViewStartDateSelect, ViewStartDateSelect,
ViewStateSelect, ViewStateSelect,
CreateUpdateDraftIssueModal,
} from "components/issues"; } from "components/issues";
// ui // ui
import { Tooltip, CustomMenu, ContextMenu } from "components/ui"; import { Tooltip, CustomMenu, ContextMenu } from "components/ui";
@ -62,6 +62,7 @@ type Props = {
removeIssue?: (() => void) | null; removeIssue?: (() => void) | null;
handleDeleteIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void;
handleDraftIssueSelect?: (issue: IIssue) => void; handleDraftIssueSelect?: (issue: IIssue) => void;
handleDraftIssueDelete?: (issue: IIssue) => void;
handleMyIssueOpen?: (issue: IIssue) => void; handleMyIssueOpen?: (issue: IIssue) => void;
disableUserActions: boolean; disableUserActions: boolean;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
@ -77,6 +78,7 @@ export const SingleListIssue: React.FC<Props> = ({
makeIssueCopy, makeIssueCopy,
removeIssue, removeIssue,
groupTitle, groupTitle,
handleDraftIssueDelete,
handleDeleteIssue, handleDeleteIssue,
handleMyIssueOpen, handleMyIssueOpen,
disableUserActions, disableUserActions,
@ -208,26 +210,45 @@ export const SingleListIssue: React.FC<Props> = ({
> >
{!isNotAllowed && ( {!isNotAllowed && (
<> <>
<ContextMenu.Item Icon={PencilIcon} onClick={editIssue}> <ContextMenu.Item
Icon={PencilIcon}
onClick={() => {
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
else editIssue();
}}
>
Edit issue Edit issue
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}> {!isDraftIssues && (
Make a copy... <ContextMenu.Item Icon={ClipboardDocumentCheckIcon} onClick={makeIssueCopy}>
</ContextMenu.Item> Make a copy...
<ContextMenu.Item Icon={TrashIcon} onClick={() => handleDeleteIssue(issue)}> </ContextMenu.Item>
)}
<ContextMenu.Item
Icon={TrashIcon}
onClick={() => {
if (isDraftIssues && handleDraftIssueDelete) handleDraftIssueDelete(issue);
else handleDeleteIssue(issue);
}}
>
Delete issue Delete issue
</ContextMenu.Item> </ContextMenu.Item>
</> </>
)} )}
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}> {!isDraftIssues && (
Copy issue link <>
</ContextMenu.Item> <ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
<a href={issuePath} target="_blank" rel="noreferrer noopener"> Copy issue link
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}> </ContextMenu.Item>
Open issue in new tab <a href={issuePath} target="_blank" rel="noreferrer noopener">
</ContextMenu.Item> <ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
</a> Open issue in new tab
</ContextMenu.Item>
</a>
</>
)}
</ContextMenu> </ContextMenu>
<div <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" 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) => { onContextMenu={(e) => {
@ -254,8 +275,7 @@ export const SingleListIssue: React.FC<Props> = ({
className="truncate text-[0.825rem] text-custom-text-100" className="truncate text-[0.825rem] text-custom-text-100"
onClick={() => { onClick={() => {
if (!isDraftIssues) openPeekOverview(issue); if (!isDraftIssues) openPeekOverview(issue);
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
if (handleDraftIssueSelect) handleDraftIssueSelect(issue);
}} }}
> >
{issue.name} {issue.name}
@ -354,7 +374,12 @@ export const SingleListIssue: React.FC<Props> = ({
)} )}
{type && !isNotAllowed && ( {type && !isNotAllowed && (
<CustomMenu width="auto" ellipsis> <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"> <div className="flex items-center justify-start gap-2">
<PencilIcon className="h-4 w-4" /> <PencilIcon className="h-4 w-4" />
<span>Edit issue</span> <span>Edit issue</span>
@ -368,18 +393,25 @@ export const SingleListIssue: React.FC<Props> = ({
</div> </div>
</CustomMenu.MenuItem> </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"> <div className="flex items-center justify-start gap-2">
<TrashIcon className="h-4 w-4" /> <TrashIcon className="h-4 w-4" />
<span>Delete issue</span> <span>Delete issue</span>
</div> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}> {!isDraftIssues && (
<div className="flex items-center justify-start gap-2"> <CustomMenu.MenuItem onClick={handleCopyText}>
<LinkIcon className="h-4 w-4" /> <div className="flex items-center justify-start gap-2">
<span>Copy issue link</span> <LinkIcon className="h-4 w-4" />
</div> <span>Copy issue link</span>
</CustomMenu.MenuItem> </div>
</CustomMenu.MenuItem>
)}
</CustomMenu> </CustomMenu>
)} )}
</div> </div>

View File

@ -39,7 +39,8 @@ type Props = {
currentState?: IState | null; currentState?: IState | null;
groupTitle: string; groupTitle: string;
addIssueToGroup: () => void; 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; openIssuesListModal?: (() => void) | null;
handleMyIssueOpen?: (issue: IIssue) => void; handleMyIssueOpen?: (issue: IIssue) => void;
removeIssue: ((bridgeId: string, issueId: string) => void) | null; removeIssue: ((bridgeId: string, issueId: string) => void) | null;
@ -56,6 +57,7 @@ export const SingleList: React.FC<Props> = ({
addIssueToGroup, addIssueToGroup,
handleIssueAction, handleIssueAction,
openIssuesListModal, openIssuesListModal,
handleDraftIssueAction,
handleMyIssueOpen, handleMyIssueOpen,
removeIssue, removeIssue,
disableUserActions, disableUserActions,
@ -253,7 +255,16 @@ export const SingleList: React.FC<Props> = ({
editIssue={() => handleIssueAction(issue, "edit")} editIssue={() => handleIssueAction(issue, "edit")}
makeIssueCopy={() => handleIssueAction(issue, "copy")} makeIssueCopy={() => handleIssueAction(issue, "copy")}
handleDeleteIssue={() => handleIssueAction(issue, "delete")} handleDeleteIssue={() => handleIssueAction(issue, "delete")}
handleDraftIssueSelect={() => handleIssueAction(issue, "updateDraft")} handleDraftIssueSelect={
handleDraftIssueAction
? () => handleDraftIssueAction(issue, "edit")
: undefined
}
handleDraftIssueDelete={
handleDraftIssueAction
? () => handleDraftIssueAction(issue, "delete")
: undefined
}
handleMyIssueOpen={handleMyIssueOpen} handleMyIssueOpen={handleMyIssueOpen}
removeIssue={() => { removeIssue={() => {
if (removeIssue !== null && issue.bridge_id) if (removeIssue !== null && issue.bridge_id)

View 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>
);
};

View File

@ -133,9 +133,15 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
const issueName = watch("name"); const issueName = watch("name");
const payload = { const payload: Partial<IIssue> = {
name: getValues("name"), name: getValues("name"),
description: getValues("description"), description: getValues("description"),
state: getValues("state"),
priority: getValues("priority"),
assignees: getValues("assignees"),
target_date: getValues("target_date"),
labels: getValues("labels"),
project: getValues("project"),
}; };
useEffect(() => { useEffect(() => {

View File

@ -17,5 +17,8 @@ export * from "./label";
export * from "./issue-reaction"; export * from "./issue-reaction";
export * from "./peek-overview"; export * from "./peek-overview";
export * from "./confirm-issue-discard"; export * from "./confirm-issue-discard";
// draft issue
export * from "./draft-issue-form"; export * from "./draft-issue-form";
export * from "./draft-issue-modal"; export * from "./draft-issue-modal";
export * from "./delete-draft-issue-modal";

View File

@ -38,6 +38,9 @@ export const WorkspaceSidebarQuickAction = () => {
"priority", "priority",
"dueDate", "dueDate",
"priority", "priority",
"state",
"startDate",
"project",
]} ]}
/> />
@ -47,7 +50,7 @@ export const WorkspaceSidebarQuickAction = () => {
}`} }`}
> >
<div <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 store?.theme?.sidebarCollapsed
? "px-2 hover:bg-custom-sidebar-background-80" ? "px-2 hover:bg-custom-sidebar-background-80"
: "px-3 shadow border-[0.5px] border-custom-border-300" : "px-3 shadow border-[0.5px] border-custom-border-300"
@ -80,7 +83,7 @@ export const WorkspaceSidebarQuickAction = () => {
<div> <div>
<Menu.Button <Menu.Button
type="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" open ? "rotate-180 pl-0" : "rotate-0 pr-0"
}`} }`}
> >
@ -108,7 +111,7 @@ export const WorkspaceSidebarQuickAction = () => {
> >
<PenSquare <PenSquare
size={16} 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 Last Drafted Issue
</button> </button>

View File

@ -55,7 +55,7 @@ const ProjectDraftIssues: NextPage = () => {
<button <button
type="button" type="button"
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/`)} 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" /> <PenSquare className="h-3 w-3 text-custom-text-300" />
<span>Draft Issues</span> <span>Draft Issues</span>