forked from github/plane
feat: drag and drop an issue to delete (#270)
* feat: drag and drop an issue to delete * style: repositioned trash box
This commit is contained in:
parent
0a88b3ed84
commit
d0afa486c7
@ -1,5 +1,3 @@
|
|||||||
// react-beautiful-dnd
|
|
||||||
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useIssueView from "hooks/use-issue-view";
|
import useIssueView from "hooks/use-issue-view";
|
||||||
// components
|
// components
|
||||||
@ -15,7 +13,7 @@ type Props = {
|
|||||||
addIssueToState: (groupTitle: string, stateId: string | null) => void;
|
addIssueToState: (groupTitle: string, stateId: string | null) => void;
|
||||||
openIssuesListModal?: (() => void) | null;
|
openIssuesListModal?: (() => void) | null;
|
||||||
handleDeleteIssue: (issue: IIssue) => void;
|
handleDeleteIssue: (issue: IIssue) => void;
|
||||||
handleOnDragEnd: (result: DropResult) => void;
|
handleTrashBox: (isDragging: boolean) => void;
|
||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ export const AllBoards: React.FC<Props> = ({
|
|||||||
addIssueToState,
|
addIssueToState,
|
||||||
openIssuesListModal,
|
openIssuesListModal,
|
||||||
handleDeleteIssue,
|
handleDeleteIssue,
|
||||||
handleOnDragEnd,
|
handleTrashBox,
|
||||||
userAuth,
|
userAuth,
|
||||||
}) => {
|
}) => {
|
||||||
const { groupedByIssues, groupByProperty: selectedGroup, orderBy } = useIssueView(issues);
|
const { groupedByIssues, groupByProperty: selectedGroup, orderBy } = useIssueView(issues);
|
||||||
@ -36,42 +34,41 @@ export const AllBoards: React.FC<Props> = ({
|
|||||||
<>
|
<>
|
||||||
{groupedByIssues ? (
|
{groupedByIssues ? (
|
||||||
<div className="h-[calc(100vh-157px)] lg:h-[calc(100vh-115px)] w-full">
|
<div className="h-[calc(100vh-157px)] lg:h-[calc(100vh-115px)] w-full">
|
||||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
<div className="h-full w-full overflow-hidden">
|
||||||
<div className="h-full w-full overflow-hidden">
|
<div className="h-full w-full">
|
||||||
<div className="h-full w-full">
|
<div className="flex h-full gap-x-4 overflow-x-auto overflow-y-hidden">
|
||||||
<div className="flex h-full gap-x-4 overflow-x-auto overflow-y-hidden">
|
{Object.keys(groupedByIssues).map((singleGroup, index) => {
|
||||||
{Object.keys(groupedByIssues).map((singleGroup, index) => {
|
const stateId =
|
||||||
const stateId =
|
selectedGroup === "state_detail.name"
|
||||||
selectedGroup === "state_detail.name"
|
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
||||||
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
: null;
|
||||||
: null;
|
|
||||||
|
|
||||||
const bgColor =
|
const bgColor =
|
||||||
selectedGroup === "state_detail.name"
|
selectedGroup === "state_detail.name"
|
||||||
? states?.find((s) => s.name === singleGroup)?.color
|
? states?.find((s) => s.name === singleGroup)?.color
|
||||||
: "#000000";
|
: "#000000";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleBoard
|
<SingleBoard
|
||||||
key={index}
|
key={index}
|
||||||
type={type}
|
type={type}
|
||||||
bgColor={bgColor}
|
bgColor={bgColor}
|
||||||
groupTitle={singleGroup}
|
groupTitle={singleGroup}
|
||||||
groupedByIssues={groupedByIssues}
|
groupedByIssues={groupedByIssues}
|
||||||
selectedGroup={selectedGroup}
|
selectedGroup={selectedGroup}
|
||||||
members={members}
|
members={members}
|
||||||
addIssueToState={() => addIssueToState(singleGroup, stateId)}
|
addIssueToState={() => addIssueToState(singleGroup, stateId)}
|
||||||
handleDeleteIssue={handleDeleteIssue}
|
handleDeleteIssue={handleDeleteIssue}
|
||||||
openIssuesListModal={openIssuesListModal ?? null}
|
openIssuesListModal={openIssuesListModal ?? null}
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
userAuth={userAuth}
|
handleTrashBox={handleTrashBox}
|
||||||
/>
|
userAuth={userAuth}
|
||||||
);
|
/>
|
||||||
})}
|
);
|
||||||
</div>
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DragDropContext>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full items-center justify-center">Loading...</div>
|
<div className="flex h-full w-full items-center justify-center">Loading...</div>
|
||||||
|
@ -29,6 +29,7 @@ type Props = {
|
|||||||
handleDeleteIssue: (issue: IIssue) => void;
|
handleDeleteIssue: (issue: IIssue) => void;
|
||||||
openIssuesListModal?: (() => void) | null;
|
openIssuesListModal?: (() => void) | null;
|
||||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||||
|
handleTrashBox: (isDragging: boolean) => void;
|
||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ export const SingleBoard: React.FC<Props> = ({
|
|||||||
handleDeleteIssue,
|
handleDeleteIssue,
|
||||||
openIssuesListModal,
|
openIssuesListModal,
|
||||||
orderBy,
|
orderBy,
|
||||||
|
handleTrashBox,
|
||||||
userAuth,
|
userAuth,
|
||||||
}) => {
|
}) => {
|
||||||
// collapse/expand
|
// collapse/expand
|
||||||
@ -89,17 +91,27 @@ export const SingleBoard: React.FC<Props> = ({
|
|||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
>
|
>
|
||||||
{groupedByIssues[groupTitle].map((issue, index: number) => (
|
{groupedByIssues[groupTitle].map((issue, index: number) => (
|
||||||
<SingleBoardIssue
|
<Draggable
|
||||||
key={index}
|
key={issue.id}
|
||||||
|
draggableId={issue.id}
|
||||||
index={index}
|
index={index}
|
||||||
type={type}
|
isDragDisabled={selectedGroup === "created_by"}
|
||||||
issue={issue}
|
>
|
||||||
selectedGroup={selectedGroup}
|
{(provided, snapshot) => (
|
||||||
properties={properties}
|
<SingleBoardIssue
|
||||||
handleDeleteIssue={handleDeleteIssue}
|
key={index}
|
||||||
orderBy={orderBy}
|
provided={provided}
|
||||||
userAuth={userAuth}
|
snapshot={snapshot}
|
||||||
/>
|
type={type}
|
||||||
|
issue={issue}
|
||||||
|
properties={properties}
|
||||||
|
handleDeleteIssue={handleDeleteIssue}
|
||||||
|
orderBy={orderBy}
|
||||||
|
handleTrashBox={handleTrashBox}
|
||||||
|
userAuth={userAuth}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
))}
|
))}
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR, { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import {
|
import {
|
||||||
Draggable,
|
DraggableProvided,
|
||||||
DraggableStateSnapshot,
|
DraggableStateSnapshot,
|
||||||
DraggingStyle,
|
DraggingStyle,
|
||||||
NotDraggingStyle,
|
NotDraggingStyle,
|
||||||
@ -37,24 +37,26 @@ import {
|
|||||||
import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
index: number;
|
|
||||||
type?: string;
|
type?: string;
|
||||||
|
provided: DraggableProvided;
|
||||||
|
snapshot: DraggableStateSnapshot;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
selectedGroup: NestedKeyOf<IIssue> | null;
|
|
||||||
properties: Properties;
|
properties: Properties;
|
||||||
handleDeleteIssue: (issue: IIssue) => void;
|
handleDeleteIssue: (issue: IIssue) => void;
|
||||||
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
orderBy: NestedKeyOf<IIssue> | "manual" | null;
|
||||||
|
handleTrashBox: (isDragging: boolean) => void;
|
||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SingleBoardIssue: React.FC<Props> = ({
|
export const SingleBoardIssue: React.FC<Props> = ({
|
||||||
index,
|
|
||||||
type,
|
type,
|
||||||
|
provided,
|
||||||
|
snapshot,
|
||||||
issue,
|
issue,
|
||||||
selectedGroup,
|
|
||||||
properties,
|
properties,
|
||||||
handleDeleteIssue,
|
handleDeleteIssue,
|
||||||
orderBy,
|
orderBy,
|
||||||
|
handleTrashBox,
|
||||||
userAuth,
|
userAuth,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -151,90 +153,84 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
|
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (snapshot.isDragging) handleTrashBox(snapshot.isDragging);
|
||||||
|
}, [snapshot, handleTrashBox]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<div
|
||||||
key={issue.id}
|
className={`rounded border bg-white shadow-sm ${
|
||||||
draggableId={issue.id}
|
snapshot.isDragging ? "border-theme bg-indigo-50 shadow-lg" : ""
|
||||||
index={index}
|
}`}
|
||||||
isDragDisabled={selectedGroup === "created_by"}
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
style={getStyle(provided.draggableProps.style, snapshot)}
|
||||||
>
|
>
|
||||||
{(provided, snapshot) => (
|
<div className="group/card relative select-none p-2">
|
||||||
<div
|
{!isNotAllowed && (
|
||||||
className={`rounded border bg-white shadow-sm ${
|
<div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover/card:opacity-100">
|
||||||
snapshot.isDragging ? "border-theme bg-indigo-50 shadow-lg" : ""
|
<button
|
||||||
}`}
|
type="button"
|
||||||
ref={provided.innerRef}
|
className="grid h-7 w-7 place-items-center rounded bg-white p-1 text-red-500 outline-none duration-300 hover:bg-red-50"
|
||||||
{...provided.draggableProps}
|
onClick={() => handleDeleteIssue(issue)}
|
||||||
{...provided.dragHandleProps}
|
>
|
||||||
style={getStyle(provided.draggableProps.style, snapshot)}
|
<TrashIcon className="h-4 w-4" />
|
||||||
>
|
</button>
|
||||||
<div className="group/card relative select-none p-2">
|
</div>
|
||||||
{!isNotAllowed && (
|
)}
|
||||||
<div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover/card:opacity-100">
|
<Link href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}>
|
||||||
<button
|
<a>
|
||||||
type="button"
|
{properties.key && (
|
||||||
className="grid h-7 w-7 place-items-center rounded bg-white p-1 text-red-500 outline-none duration-300 hover:bg-red-50"
|
<div className="mb-2 text-xs font-medium text-gray-500">
|
||||||
onClick={() => handleDeleteIssue(issue)}
|
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||||
>
|
|
||||||
<TrashIcon className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Link href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}>
|
<h5
|
||||||
<a>
|
className="mb-3 text-sm group-hover:text-theme"
|
||||||
{properties.key && (
|
style={{ lineClamp: 3, WebkitLineClamp: 3 }}
|
||||||
<div className="mb-2 text-xs font-medium text-gray-500">
|
>
|
||||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
{issue.name}
|
||||||
</div>
|
</h5>
|
||||||
)}
|
</a>
|
||||||
<h5
|
</Link>
|
||||||
className="mb-3 text-sm group-hover:text-theme"
|
<div className="flex flex-wrap items-center gap-x-1 gap-y-2 text-xs">
|
||||||
style={{ lineClamp: 3, WebkitLineClamp: 3 }}
|
{properties.priority && (
|
||||||
>
|
<ViewPrioritySelect
|
||||||
{issue.name}
|
issue={issue}
|
||||||
</h5>
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
</a>
|
isNotAllowed={isNotAllowed}
|
||||||
</Link>
|
position="left"
|
||||||
<div className="flex flex-wrap items-center gap-x-1 gap-y-2 text-xs">
|
/>
|
||||||
{properties.priority && (
|
)}
|
||||||
<ViewPrioritySelect
|
{properties.state && (
|
||||||
issue={issue}
|
<ViewStateSelect
|
||||||
partialUpdateIssue={partialUpdateIssue}
|
issue={issue}
|
||||||
isNotAllowed={isNotAllowed}
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
position="left"
|
isNotAllowed={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{properties.state && (
|
{properties.due_date && (
|
||||||
<ViewStateSelect
|
<ViewDueDateSelect
|
||||||
issue={issue}
|
issue={issue}
|
||||||
partialUpdateIssue={partialUpdateIssue}
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
isNotAllowed={isNotAllowed}
|
isNotAllowed={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{properties.due_date && (
|
{properties.sub_issue_count && (
|
||||||
<ViewDueDateSelect
|
<div className="flex flex-shrink-0 items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||||
issue={issue}
|
{issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
|
||||||
partialUpdateIssue={partialUpdateIssue}
|
|
||||||
isNotAllowed={isNotAllowed}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{properties.sub_issue_count && (
|
|
||||||
<div className="flex flex-shrink-0 items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
||||||
{issue.sub_issues_count}{" "}
|
|
||||||
{issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{properties.assignee && (
|
|
||||||
<ViewAssigneeSelect
|
|
||||||
issue={issue}
|
|
||||||
partialUpdateIssue={partialUpdateIssue}
|
|
||||||
isNotAllowed={isNotAllowed}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
{properties.assignee && (
|
||||||
|
<ViewAssigneeSelect
|
||||||
|
issue={issue}
|
||||||
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
|
isNotAllowed={isNotAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</Draggable>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DropResult } from "react-beautiful-dnd";
|
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/state.service";
|
||||||
@ -16,6 +16,9 @@ import useIssueView from "hooks/use-issue-view";
|
|||||||
// components
|
// components
|
||||||
import { AllLists, AllBoards } from "components/core";
|
import { AllLists, AllBoards } from "components/core";
|
||||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
||||||
|
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||||
|
// icons
|
||||||
|
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { getStatesList } from "helpers/state.helper";
|
import { getStatesList } from "helpers/state.helper";
|
||||||
// types
|
// types
|
||||||
@ -58,6 +61,9 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
|
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
|
||||||
|
|
||||||
|
// trash box
|
||||||
|
const [trashBox, setTrashBox] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
@ -78,271 +84,308 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteIssue = useCallback(
|
||||||
|
(issue: IIssue) => {
|
||||||
|
setDeleteIssueModal(true);
|
||||||
|
setIssueToDelete(issue);
|
||||||
|
},
|
||||||
|
[setDeleteIssueModal, setIssueToDelete]
|
||||||
|
);
|
||||||
|
|
||||||
const handleOnDragEnd = useCallback(
|
const handleOnDragEnd = useCallback(
|
||||||
(result: DropResult) => {
|
(result: DropResult) => {
|
||||||
|
setTrashBox(false);
|
||||||
|
|
||||||
if (!result.destination || !workspaceSlug || !projectId) return;
|
if (!result.destination || !workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
const draggedItem = groupedByIssues[source.droppableId][source.index];
|
const draggedItem = groupedByIssues[source.droppableId][source.index];
|
||||||
|
|
||||||
if (source.droppableId !== destination.droppableId) {
|
if (destination.droppableId === "trashBox") {
|
||||||
const sourceGroup = source.droppableId; // source group id
|
handleDeleteIssue(draggedItem);
|
||||||
const destinationGroup = destination.droppableId; // destination group id
|
} else {
|
||||||
|
if (source.droppableId !== destination.droppableId) {
|
||||||
|
const sourceGroup = source.droppableId; // source group id
|
||||||
|
const destinationGroup = destination.droppableId; // destination group id
|
||||||
|
|
||||||
if (!sourceGroup || !destinationGroup) return;
|
if (!sourceGroup || !destinationGroup) return;
|
||||||
|
|
||||||
if (selectedGroup === "priority") {
|
if (selectedGroup === "priority") {
|
||||||
// update the removed item for mutation
|
// update the removed item for mutation
|
||||||
draggedItem.priority = destinationGroup;
|
draggedItem.priority = destinationGroup;
|
||||||
|
|
||||||
if (cycleId)
|
if (cycleId)
|
||||||
mutate<CycleIssueResponse[]>(
|
mutate<CycleIssueResponse[]>(
|
||||||
CYCLE_ISSUES(cycleId as string),
|
CYCLE_ISSUES(cycleId as string),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
const updatedIssues = prevData.map((issue) => {
|
||||||
|
if (issue.issue_detail.id === draggedItem.id) {
|
||||||
|
return {
|
||||||
|
...issue,
|
||||||
|
issue_detail: {
|
||||||
|
...draggedItem,
|
||||||
|
priority: destinationGroup,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return issue;
|
||||||
|
});
|
||||||
|
return [...updatedIssues];
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleId)
|
||||||
|
mutate<ModuleIssueResponse[]>(
|
||||||
|
MODULE_ISSUES(moduleId as string),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
const updatedIssues = prevData.map((issue) => {
|
||||||
|
if (issue.issue_detail.id === draggedItem.id) {
|
||||||
|
return {
|
||||||
|
...issue,
|
||||||
|
issue_detail: {
|
||||||
|
...draggedItem,
|
||||||
|
priority: destinationGroup,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return issue;
|
||||||
|
});
|
||||||
|
return [...updatedIssues];
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
mutate<IssueResponse>(
|
||||||
|
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
|
||||||
(prevData) => {
|
(prevData) => {
|
||||||
if (!prevData) return prevData;
|
if (!prevData) return prevData;
|
||||||
const updatedIssues = prevData.map((issue) => {
|
|
||||||
if (issue.issue_detail.id === draggedItem.id) {
|
const updatedIssues = prevData.results.map((issue) => {
|
||||||
|
if (issue.id === draggedItem.id)
|
||||||
return {
|
return {
|
||||||
...issue,
|
...draggedItem,
|
||||||
issue_detail: {
|
priority: destinationGroup,
|
||||||
...draggedItem,
|
|
||||||
priority: destinationGroup,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
return issue;
|
return issue;
|
||||||
});
|
});
|
||||||
return [...updatedIssues];
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
results: updatedIssues,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
if (moduleId)
|
// patch request
|
||||||
mutate<ModuleIssueResponse[]>(
|
issuesService
|
||||||
MODULE_ISSUES(moduleId as string),
|
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
|
||||||
(prevData) => {
|
priority: destinationGroup,
|
||||||
if (!prevData) return prevData;
|
})
|
||||||
const updatedIssues = prevData.map((issue) => {
|
.then((res) => {
|
||||||
if (issue.issue_detail.id === draggedItem.id) {
|
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
||||||
return {
|
if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
|
||||||
...issue,
|
|
||||||
issue_detail: {
|
|
||||||
...draggedItem,
|
|
||||||
priority: destinationGroup,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return issue;
|
|
||||||
});
|
|
||||||
return [...updatedIssues];
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
mutate<IssueResponse>(
|
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
|
||||||
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
const updatedIssues = prevData.results.map((issue) => {
|
|
||||||
if (issue.id === draggedItem.id)
|
|
||||||
return {
|
|
||||||
...draggedItem,
|
|
||||||
priority: destinationGroup,
|
|
||||||
};
|
|
||||||
|
|
||||||
return issue;
|
|
||||||
});
|
});
|
||||||
|
} else if (selectedGroup === "state_detail.name") {
|
||||||
|
const destinationState = states?.find((s) => s.name === destinationGroup);
|
||||||
|
const destinationStateId = destinationState?.id;
|
||||||
|
|
||||||
return {
|
// update the removed item for mutation
|
||||||
...prevData,
|
if (!destinationStateId || !destinationState) return;
|
||||||
results: updatedIssues,
|
draggedItem.state = destinationStateId;
|
||||||
};
|
draggedItem.state_detail = destinationState;
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
// patch request
|
if (cycleId)
|
||||||
issuesService
|
mutate<CycleIssueResponse[]>(
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
|
CYCLE_ISSUES(cycleId as string),
|
||||||
priority: destinationGroup,
|
(prevData) => {
|
||||||
})
|
if (!prevData) return prevData;
|
||||||
.then((res) => {
|
const updatedIssues = prevData.map((issue) => {
|
||||||
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
if (issue.issue_detail.id === draggedItem.id) {
|
||||||
if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
|
return {
|
||||||
|
...issue,
|
||||||
|
issue_detail: {
|
||||||
|
...draggedItem,
|
||||||
|
state_detail: destinationState,
|
||||||
|
state: destinationStateId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return issue;
|
||||||
|
});
|
||||||
|
return [...updatedIssues];
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
|
if (moduleId)
|
||||||
});
|
mutate<ModuleIssueResponse[]>(
|
||||||
} else if (selectedGroup === "state_detail.name") {
|
MODULE_ISSUES(moduleId as string),
|
||||||
const destinationState = states?.find((s) => s.name === destinationGroup);
|
(prevData) => {
|
||||||
const destinationStateId = destinationState?.id;
|
if (!prevData) return prevData;
|
||||||
|
const updatedIssues = prevData.map((issue) => {
|
||||||
|
if (issue.issue_detail.id === draggedItem.id) {
|
||||||
|
return {
|
||||||
|
...issue,
|
||||||
|
issue_detail: {
|
||||||
|
...draggedItem,
|
||||||
|
state_detail: destinationState,
|
||||||
|
state: destinationStateId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return issue;
|
||||||
|
});
|
||||||
|
return [...updatedIssues];
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
// update the removed item for mutation
|
mutate<IssueResponse>(
|
||||||
if (!destinationStateId || !destinationState) return;
|
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
|
||||||
draggedItem.state = destinationStateId;
|
|
||||||
draggedItem.state_detail = destinationState;
|
|
||||||
|
|
||||||
if (cycleId)
|
|
||||||
mutate<CycleIssueResponse[]>(
|
|
||||||
CYCLE_ISSUES(cycleId as string),
|
|
||||||
(prevData) => {
|
(prevData) => {
|
||||||
if (!prevData) return prevData;
|
if (!prevData) return prevData;
|
||||||
const updatedIssues = prevData.map((issue) => {
|
|
||||||
if (issue.issue_detail.id === draggedItem.id) {
|
const updatedIssues = prevData.results.map((issue) => {
|
||||||
|
if (issue.id === draggedItem.id)
|
||||||
return {
|
return {
|
||||||
...issue,
|
...draggedItem,
|
||||||
issue_detail: {
|
state_detail: destinationState,
|
||||||
...draggedItem,
|
state: destinationStateId,
|
||||||
state_detail: destinationState,
|
|
||||||
state: destinationStateId,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
return issue;
|
return issue;
|
||||||
});
|
});
|
||||||
return [...updatedIssues];
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
results: updatedIssues,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
if (moduleId)
|
// patch request
|
||||||
mutate<ModuleIssueResponse[]>(
|
issuesService
|
||||||
MODULE_ISSUES(moduleId as string),
|
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
|
||||||
(prevData) => {
|
state: destinationStateId,
|
||||||
if (!prevData) return prevData;
|
})
|
||||||
const updatedIssues = prevData.map((issue) => {
|
.then((res) => {
|
||||||
if (issue.issue_detail.id === draggedItem.id) {
|
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
||||||
return {
|
if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
|
||||||
...issue,
|
|
||||||
issue_detail: {
|
|
||||||
...draggedItem,
|
|
||||||
state_detail: destinationState,
|
|
||||||
state: destinationStateId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return issue;
|
|
||||||
});
|
|
||||||
return [...updatedIssues];
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
mutate<IssueResponse>(
|
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
|
||||||
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
const updatedIssues = prevData.results.map((issue) => {
|
|
||||||
if (issue.id === draggedItem.id)
|
|
||||||
return {
|
|
||||||
...draggedItem,
|
|
||||||
state_detail: destinationState,
|
|
||||||
state: destinationStateId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return issue;
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
results: updatedIssues,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
// patch request
|
|
||||||
issuesService
|
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
|
|
||||||
state: destinationStateId,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (cycleId) mutate(CYCLE_ISSUES(cycleId as string));
|
|
||||||
if (moduleId) mutate(MODULE_ISSUES(moduleId as string));
|
|
||||||
|
|
||||||
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[workspaceSlug, cycleId, moduleId, groupedByIssues, projectId, selectedGroup, states]
|
[
|
||||||
|
workspaceSlug,
|
||||||
|
cycleId,
|
||||||
|
moduleId,
|
||||||
|
groupedByIssues,
|
||||||
|
projectId,
|
||||||
|
selectedGroup,
|
||||||
|
states,
|
||||||
|
handleDeleteIssue,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const addIssueToState = (groupTitle: string, stateId: string | null) => {
|
const addIssueToState = useCallback(
|
||||||
setCreateIssueModal(true);
|
(groupTitle: string, stateId: string | null) => {
|
||||||
if (selectedGroup)
|
setCreateIssueModal(true);
|
||||||
setPreloadedData({
|
if (selectedGroup)
|
||||||
state: stateId ?? undefined,
|
setPreloadedData({
|
||||||
[selectedGroup]: groupTitle,
|
state: stateId ?? undefined,
|
||||||
actionType: "createIssue",
|
[selectedGroup]: groupTitle,
|
||||||
|
actionType: "createIssue",
|
||||||
|
});
|
||||||
|
else setPreloadedData({ actionType: "createIssue" });
|
||||||
|
},
|
||||||
|
[setCreateIssueModal, setPreloadedData, selectedGroup]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEditIssue = useCallback(
|
||||||
|
(issue: IIssue) => {
|
||||||
|
setEditIssueModal(true);
|
||||||
|
setIssueToEdit({
|
||||||
|
...issue,
|
||||||
|
actionType: "edit",
|
||||||
|
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
|
||||||
|
module: issue.issue_module ? issue.issue_module.module : null,
|
||||||
});
|
});
|
||||||
else setPreloadedData({ actionType: "createIssue" });
|
},
|
||||||
};
|
[setEditIssueModal, setIssueToEdit]
|
||||||
|
);
|
||||||
|
|
||||||
const handleEditIssue = (issue: IIssue) => {
|
const removeIssueFromCycle = useCallback(
|
||||||
setEditIssueModal(true);
|
(bridgeId: string) => {
|
||||||
setIssueToEdit({
|
if (!workspaceSlug || !projectId) return;
|
||||||
...issue,
|
|
||||||
actionType: "edit",
|
|
||||||
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
|
|
||||||
module: issue.issue_module ? issue.issue_module.module : null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteIssue = (issue: IIssue) => {
|
mutate<CycleIssueResponse[]>(
|
||||||
setDeleteIssueModal(true);
|
CYCLE_ISSUES(cycleId as string),
|
||||||
setIssueToDelete(issue);
|
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
||||||
};
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const removeIssueFromCycle = (bridgeId: string) => {
|
issuesService
|
||||||
if (!workspaceSlug || !projectId) return;
|
.removeIssueFromCycle(
|
||||||
|
workspaceSlug as string,
|
||||||
|
projectId as string,
|
||||||
|
cycleId as string,
|
||||||
|
bridgeId
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, cycleId]
|
||||||
|
);
|
||||||
|
|
||||||
mutate<CycleIssueResponse[]>(
|
const removeIssueFromModule = useCallback(
|
||||||
CYCLE_ISSUES(cycleId as string),
|
(bridgeId: string) => {
|
||||||
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
if (!workspaceSlug || !projectId) return;
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
issuesService
|
mutate<ModuleIssueResponse[]>(
|
||||||
.removeIssueFromCycle(
|
MODULE_ISSUES(moduleId as string),
|
||||||
workspaceSlug as string,
|
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
||||||
projectId as string,
|
false
|
||||||
cycleId as string,
|
);
|
||||||
bridgeId
|
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
console.log(res);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeIssueFromModule = (bridgeId: string) => {
|
modulesService
|
||||||
if (!workspaceSlug || !projectId) return;
|
.removeIssueFromModule(
|
||||||
|
workspaceSlug as string,
|
||||||
|
projectId as string,
|
||||||
|
moduleId as string,
|
||||||
|
bridgeId
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, moduleId]
|
||||||
|
);
|
||||||
|
|
||||||
mutate<ModuleIssueResponse[]>(
|
const handleTrashBox = useCallback(
|
||||||
MODULE_ISSUES(moduleId as string),
|
(isDragging: boolean) => {
|
||||||
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
|
if (isDragging && !trashBox) setTrashBox(true);
|
||||||
false
|
},
|
||||||
);
|
[trashBox, setTrashBox]
|
||||||
|
);
|
||||||
modulesService
|
|
||||||
.removeIssueFromModule(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
moduleId as string,
|
|
||||||
bridgeId
|
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
console.log(res);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -364,38 +407,59 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
isOpen={deleteIssueModal}
|
isOpen={deleteIssueModal}
|
||||||
data={issueToDelete}
|
data={issueToDelete}
|
||||||
/>
|
/>
|
||||||
{issueView === "list" ? (
|
|
||||||
<AllLists
|
<div className="relative">
|
||||||
type={type}
|
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||||
issues={issues}
|
<StrictModeDroppable droppableId="trashBox">
|
||||||
states={states}
|
{(provided, snapshot) => (
|
||||||
members={members}
|
<div
|
||||||
addIssueToState={addIssueToState}
|
className={`${
|
||||||
handleEditIssue={handleEditIssue}
|
trashBox ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
|
||||||
handleDeleteIssue={handleDeleteIssue}
|
} fixed z-20 top-12 left-1/2 -translate-x-1/2 flex items-center gap-2 bg-red-100 border-2 border-red-500 p-3 text-xs rounded ${
|
||||||
openIssuesListModal={type !== "issue" ? openIssuesListModal : null}
|
snapshot.isDraggingOver ? "bg-red-500 text-white" : ""
|
||||||
removeIssue={
|
} duration-200`}
|
||||||
type === "cycle"
|
ref={provided.innerRef}
|
||||||
? removeIssueFromCycle
|
{...provided.droppableProps}
|
||||||
: type === "module"
|
>
|
||||||
? removeIssueFromModule
|
<TrashIcon className="h-3 w-3" />
|
||||||
: null
|
Drop issue here to delete
|
||||||
}
|
</div>
|
||||||
userAuth={userAuth}
|
)}
|
||||||
/>
|
</StrictModeDroppable>
|
||||||
) : (
|
{issueView === "list" ? (
|
||||||
<AllBoards
|
<AllLists
|
||||||
type={type}
|
type={type}
|
||||||
issues={issues}
|
issues={issues}
|
||||||
states={states}
|
states={states}
|
||||||
members={members}
|
members={members}
|
||||||
addIssueToState={addIssueToState}
|
addIssueToState={addIssueToState}
|
||||||
openIssuesListModal={type !== "issue" ? openIssuesListModal : null}
|
handleEditIssue={handleEditIssue}
|
||||||
handleDeleteIssue={handleDeleteIssue}
|
handleDeleteIssue={handleDeleteIssue}
|
||||||
handleOnDragEnd={handleOnDragEnd}
|
openIssuesListModal={type !== "issue" ? openIssuesListModal : null}
|
||||||
userAuth={userAuth}
|
removeIssue={
|
||||||
/>
|
type === "cycle"
|
||||||
)}
|
? removeIssueFromCycle
|
||||||
|
: type === "module"
|
||||||
|
? removeIssueFromModule
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
userAuth={userAuth}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AllBoards
|
||||||
|
type={type}
|
||||||
|
issues={issues}
|
||||||
|
states={states}
|
||||||
|
members={members}
|
||||||
|
addIssueToState={addIssueToState}
|
||||||
|
openIssuesListModal={type !== "issue" ? openIssuesListModal : null}
|
||||||
|
handleDeleteIssue={handleDeleteIssue}
|
||||||
|
handleTrashBox={handleTrashBox}
|
||||||
|
userAuth={userAuth}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user