From d0afa486c70d9ddecefee60b9add3127f9b35d05 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:32:02 +0530 Subject: [PATCH] feat: drag and drop an issue to delete (#270) * feat: drag and drop an issue to delete * style: repositioned trash box --- .../components/core/board-view/all-boards.tsx | 69 ++- .../core/board-view/single-board.tsx | 32 +- .../core/board-view/single-issue.tsx | 168 +++--- apps/app/components/core/issues-view.tsx | 556 ++++++++++-------- 4 files changed, 447 insertions(+), 378 deletions(-) diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 9aa29e7c4..d4e995403 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -1,5 +1,3 @@ -// react-beautiful-dnd -import { DragDropContext, DropResult } from "react-beautiful-dnd"; // hooks import useIssueView from "hooks/use-issue-view"; // components @@ -15,7 +13,7 @@ type Props = { addIssueToState: (groupTitle: string, stateId: string | null) => void; openIssuesListModal?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; - handleOnDragEnd: (result: DropResult) => void; + handleTrashBox: (isDragging: boolean) => void; userAuth: UserAuth; }; @@ -27,7 +25,7 @@ export const AllBoards: React.FC = ({ addIssueToState, openIssuesListModal, handleDeleteIssue, - handleOnDragEnd, + handleTrashBox, userAuth, }) => { const { groupedByIssues, groupByProperty: selectedGroup, orderBy } = useIssueView(issues); @@ -36,42 +34,41 @@ export const AllBoards: React.FC = ({ <> {groupedByIssues ? (
- -
-
-
- {Object.keys(groupedByIssues).map((singleGroup, index) => { - const stateId = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup)?.id ?? null - : null; +
+
+
+ {Object.keys(groupedByIssues).map((singleGroup, index) => { + const stateId = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.id ?? null + : null; - const bgColor = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup)?.color - : "#000000"; + const bgColor = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.color + : "#000000"; - return ( - addIssueToState(singleGroup, stateId)} - handleDeleteIssue={handleDeleteIssue} - openIssuesListModal={openIssuesListModal ?? null} - orderBy={orderBy} - userAuth={userAuth} - /> - ); - })} -
+ return ( + addIssueToState(singleGroup, stateId)} + handleDeleteIssue={handleDeleteIssue} + openIssuesListModal={openIssuesListModal ?? null} + orderBy={orderBy} + handleTrashBox={handleTrashBox} + userAuth={userAuth} + /> + ); + })}
- +
) : (
Loading...
diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx index 82d789a07..2063f6e14 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -29,6 +29,7 @@ type Props = { handleDeleteIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; orderBy: NestedKeyOf | "manual" | null; + handleTrashBox: (isDragging: boolean) => void; userAuth: UserAuth; }; @@ -43,6 +44,7 @@ export const SingleBoard: React.FC = ({ handleDeleteIssue, openIssuesListModal, orderBy, + handleTrashBox, userAuth, }) => { // collapse/expand @@ -89,17 +91,27 @@ export const SingleBoard: React.FC = ({ {...provided.droppableProps} > {groupedByIssues[groupTitle].map((issue, index: number) => ( - + isDragDisabled={selectedGroup === "created_by"} + > + {(provided, snapshot) => ( + + )} + ))} | null; properties: Properties; handleDeleteIssue: (issue: IIssue) => void; orderBy: NestedKeyOf | "manual" | null; + handleTrashBox: (isDragging: boolean) => void; userAuth: UserAuth; }; export const SingleBoardIssue: React.FC = ({ - index, type, + provided, + snapshot, issue, - selectedGroup, properties, handleDeleteIssue, orderBy, + handleTrashBox, userAuth, }) => { const router = useRouter(); @@ -151,90 +153,84 @@ export const SingleBoardIssue: React.FC = ({ const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + useEffect(() => { + if (snapshot.isDragging) handleTrashBox(snapshot.isDragging); + }, [snapshot, handleTrashBox]); + return ( - - {(provided, snapshot) => ( -
-
- {!isNotAllowed && ( -
- +
+ {!isNotAllowed && ( +
+ +
+ )} + + + {properties.key && ( +
+ {issue.project_detail.identifier}-{issue.sequence_id}
)} - -
- {properties.key && ( -
- {issue.project_detail.identifier}-{issue.sequence_id} -
- )} -
- {issue.name} -
-
- -
- {properties.priority && ( - - )} - {properties.state && ( - - )} - {properties.due_date && ( - - )} - {properties.sub_issue_count && ( -
- {issue.sub_issues_count}{" "} - {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} -
- )} - {properties.assignee && ( - - )} +
+ {issue.name} +
+ + +
+ {properties.priority && ( + + )} + {properties.state && ( + + )} + {properties.due_date && ( + + )} + {properties.sub_issue_count && ( +
+ {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
-
+ )} + {properties.assignee && ( + + )}
- )} - +
+
); }; diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index ec4c5b688..2edb7f804 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // react-beautiful-dnd -import { DropResult } from "react-beautiful-dnd"; +import { DragDropContext, DropResult } from "react-beautiful-dnd"; // services import issuesService from "services/issues.service"; import stateService from "services/state.service"; @@ -16,6 +16,9 @@ import useIssueView from "hooks/use-issue-view"; // components import { AllLists, AllBoards } from "components/core"; import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; +import StrictModeDroppable from "components/dnd/StrictModeDroppable"; +// icons +import { TrashIcon } from "@heroicons/react/24/outline"; // helpers import { getStatesList } from "helpers/state.helper"; // types @@ -58,6 +61,9 @@ export const IssuesView: React.FC = ({ const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [issueToDelete, setIssueToDelete] = useState(null); + // trash box + const [trashBox, setTrashBox] = useState(false); + const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; @@ -78,271 +84,308 @@ export const IssuesView: React.FC = ({ : null ); + const handleDeleteIssue = useCallback( + (issue: IIssue) => { + setDeleteIssueModal(true); + setIssueToDelete(issue); + }, + [setDeleteIssueModal, setIssueToDelete] + ); + const handleOnDragEnd = useCallback( (result: DropResult) => { + setTrashBox(false); + if (!result.destination || !workspaceSlug || !projectId) return; const { source, destination } = result; const draggedItem = groupedByIssues[source.droppableId][source.index]; - if (source.droppableId !== destination.droppableId) { - const sourceGroup = source.droppableId; // source group id - const destinationGroup = destination.droppableId; // destination group id + if (destination.droppableId === "trashBox") { + handleDeleteIssue(draggedItem); + } 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") { - // update the removed item for mutation - draggedItem.priority = destinationGroup; + if (selectedGroup === "priority") { + // update the removed item for mutation + draggedItem.priority = destinationGroup; - if (cycleId) - mutate( - CYCLE_ISSUES(cycleId as string), + if (cycleId) + mutate( + 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( + 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( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), (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 { - ...issue, - issue_detail: { - ...draggedItem, - priority: destinationGroup, - }, + ...draggedItem, + priority: destinationGroup, }; - } + return issue; }); - return [...updatedIssues]; + + return { + ...prevData, + results: updatedIssues, + }; }, false ); - if (moduleId) - mutate( - 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 - ); + // patch request + issuesService + .patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, { + priority: destinationGroup, + }) + .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), - (prevData) => { - if (!prevData) return prevData; - - const updatedIssues = prevData.results.map((issue) => { - if (issue.id === draggedItem.id) - return { - ...draggedItem, - priority: destinationGroup, - }; - - return issue; + mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); }); + } else if (selectedGroup === "state_detail.name") { + const destinationState = states?.find((s) => s.name === destinationGroup); + const destinationStateId = destinationState?.id; - return { - ...prevData, - results: updatedIssues, - }; - }, - false - ); + // update the removed item for mutation + if (!destinationStateId || !destinationState) return; + draggedItem.state = destinationStateId; + draggedItem.state_detail = destinationState; - // patch request - issuesService - .patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, { - priority: destinationGroup, - }) - .then((res) => { - if (cycleId) mutate(CYCLE_ISSUES(cycleId as string)); - if (moduleId) mutate(MODULE_ISSUES(moduleId as string)); + if (cycleId) + mutate( + 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, + state_detail: destinationState, + state: destinationStateId, + }, + }; + } + return issue; + }); + return [...updatedIssues]; + }, + false + ); - mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); - }); - } else if (selectedGroup === "state_detail.name") { - const destinationState = states?.find((s) => s.name === destinationGroup); - const destinationStateId = destinationState?.id; + if (moduleId) + mutate( + 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, + state_detail: destinationState, + state: destinationStateId, + }, + }; + } + return issue; + }); + return [...updatedIssues]; + }, + false + ); - // update the removed item for mutation - if (!destinationStateId || !destinationState) return; - draggedItem.state = destinationStateId; - draggedItem.state_detail = destinationState; - - if (cycleId) - mutate( - CYCLE_ISSUES(cycleId as string), + mutate( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), (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 { - ...issue, - issue_detail: { - ...draggedItem, - state_detail: destinationState, - state: destinationStateId, - }, + ...draggedItem, + state_detail: destinationState, + state: destinationStateId, }; - } + return issue; }); - return [...updatedIssues]; + + return { + ...prevData, + results: updatedIssues, + }; }, false ); - if (moduleId) - mutate( - 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, - state_detail: destinationState, - state: destinationStateId, - }, - }; - } - return issue; - }); - return [...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), - (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; + mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); }); - - 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) => { - setCreateIssueModal(true); - if (selectedGroup) - setPreloadedData({ - state: stateId ?? undefined, - [selectedGroup]: groupTitle, - actionType: "createIssue", + const addIssueToState = useCallback( + (groupTitle: string, stateId: string | null) => { + setCreateIssueModal(true); + if (selectedGroup) + setPreloadedData({ + state: stateId ?? undefined, + [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) => { - setEditIssueModal(true); - setIssueToEdit({ - ...issue, - actionType: "edit", - cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null, - module: issue.issue_module ? issue.issue_module.module : null, - }); - }; + const removeIssueFromCycle = useCallback( + (bridgeId: string) => { + if (!workspaceSlug || !projectId) return; - const handleDeleteIssue = (issue: IIssue) => { - setDeleteIssueModal(true); - setIssueToDelete(issue); - }; + mutate( + CYCLE_ISSUES(cycleId as string), + (prevData) => prevData?.filter((p) => p.id !== bridgeId), + false + ); - const removeIssueFromCycle = (bridgeId: string) => { - if (!workspaceSlug || !projectId) return; + issuesService + .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( - CYCLE_ISSUES(cycleId as string), - (prevData) => prevData?.filter((p) => p.id !== bridgeId), - false - ); + const removeIssueFromModule = useCallback( + (bridgeId: string) => { + if (!workspaceSlug || !projectId) return; - issuesService - .removeIssueFromCycle( - workspaceSlug as string, - projectId as string, - cycleId as string, - bridgeId - ) - .then((res) => { - console.log(res); - }) - .catch((e) => { - console.log(e); - }); - }; + mutate( + MODULE_ISSUES(moduleId as string), + (prevData) => prevData?.filter((p) => p.id !== bridgeId), + false + ); - const removeIssueFromModule = (bridgeId: string) => { - if (!workspaceSlug || !projectId) return; + modulesService + .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( - MODULE_ISSUES(moduleId as string), - (prevData) => prevData?.filter((p) => p.id !== bridgeId), - false - ); - - modulesService - .removeIssueFromModule( - workspaceSlug as string, - projectId as string, - moduleId as string, - bridgeId - ) - .then((res) => { - console.log(res); - }) - .catch((e) => { - console.log(e); - }); - }; + const handleTrashBox = useCallback( + (isDragging: boolean) => { + if (isDragging && !trashBox) setTrashBox(true); + }, + [trashBox, setTrashBox] + ); return ( <> @@ -364,38 +407,59 @@ export const IssuesView: React.FC = ({ isOpen={deleteIssueModal} data={issueToDelete} /> - {issueView === "list" ? ( - - ) : ( - - )} + +
+ + + {(provided, snapshot) => ( +
+ + Drop issue here to delete +
+ )} +
+ {issueView === "list" ? ( + + ) : ( + + )} +
+
); };