import { FC, useCallback, useRef, useState } from "react"; import { DragDropContext, DragStart, DraggableLocation, DropResult, Droppable } from "@hello-pangea/dnd"; import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; // hooks import { Spinner, TOAST_TYPE, setToast } from "@plane/ui"; import { DeleteIssueModal } from "components/issues"; import { ISSUE_DELETED } from "constants/event-tracker"; import { EIssueFilterType, EIssuesStoreType } from "constants/issue"; import { EUserProjectRoles } from "constants/project"; import { useEventTracker, useIssues, useUser } from "hooks/store"; import { useIssuesActions } from "hooks/use-issues-actions"; // ui // types import { TIssue } from "@plane/types"; import { IQuickActionProps } from "../list/list-view-types"; //components import { KanBan } from "./default"; import { KanBanSwimLanes } from "./swimlanes"; import { handleDragDrop } from "./utils"; export type KanbanStoreType = | EIssuesStoreType.PROJECT | EIssuesStoreType.MODULE | EIssuesStoreType.CYCLE | EIssuesStoreType.PROJECT_VIEW | EIssuesStoreType.DRAFT | EIssuesStoreType.PROFILE; export interface IBaseKanBanLayout { QuickActions: FC; showLoader?: boolean; viewId?: string; storeType: KanbanStoreType; addIssuesToView?: (issueIds: string[]) => Promise; canEditPropertiesBasedOnProject?: (projectId: string) => boolean; isCompletedCycle?: boolean; } type KanbanDragState = { draggedIssueId?: string | null; source?: DraggableLocation | null; destination?: DraggableLocation | null; }; export const BaseKanBanRoot: React.FC = observer((props: IBaseKanBanLayout) => { const { QuickActions, showLoader, viewId, storeType, addIssuesToView, canEditPropertiesBasedOnProject, isCompletedCycle = false, } = props; // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; // store hooks const { membership: { currentProjectRole }, } = useUser(); const { captureIssueEvent } = useEventTracker(); const { issueMap, issuesFilter, issues } = useIssues(storeType); const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } = useIssuesActions(storeType); const issueIds = issues?.groupedIssueIds || []; const displayFilters = issuesFilter?.issueFilters?.displayFilters; const displayProperties = issuesFilter?.issueFilters?.displayProperties; const sub_group_by: string | null = displayFilters?.sub_group_by || null; const group_by: string | null = displayFilters?.group_by || null; const userDisplayFilters = displayFilters || null; const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan; const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {}; const scrollableContainerRef = useRef(null); // states const [isDragStarted, setIsDragStarted] = useState(false); const [dragState, setDragState] = useState({}); const [deleteIssueModal, setDeleteIssueModal] = useState(false); const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const canEditProperties = useCallback( (projectId: string | undefined) => { const isEditingAllowedBasedOnProject = canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; return enableInlineEditing && isEditingAllowedBasedOnProject; }, [canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed] ); const onDragStart = (dragStart: DragStart) => { setDragState({ draggedIssueId: dragStart.draggableId.split("__")[0], }); setIsDragStarted(true); }; const onDragEnd = async (result: DropResult) => { setIsDragStarted(false); if (!result) return; if ( result.destination && result.source && result.source.droppableId && result.destination.droppableId && result.destination.droppableId === result.source.droppableId && result.destination.index === result.source.index ) return; if (handleDragDrop) { if (result.destination?.droppableId && result.destination?.droppableId.split("__")[0] === "issue-trash-box") { setDragState({ ...dragState, source: result.source, destination: result.destination, }); setDeleteIssueModal(true); } else { await handleDragDrop( result.source, result.destination, workspaceSlug?.toString(), projectId?.toString(), sub_group_by, group_by, issueMap, issueIds, updateIssue, removeIssue ).catch((err) => { setToast({ title: "Error", type: TOAST_TYPE.ERROR, message: err.detail ?? "Failed to perform this action", }); }); } } }; const renderQuickActions = useCallback( (issue: TIssue, customActionButton?: React.ReactElement) => ( removeIssue(issue.project_id, issue.id)} handleUpdate={async (data) => updateIssue && updateIssue(issue.project_id, issue.id, data)} handleRemoveFromView={async () => removeIssueFromView && removeIssueFromView(issue.project_id, issue.id)} handleArchive={async () => archiveIssue && archiveIssue(issue.project_id, issue.id)} handleRestore={async () => restoreIssue && restoreIssue(issue.project_id, issue.id)} readOnly={!isEditingAllowed || isCompletedCycle} /> ), // eslint-disable-next-line react-hooks/exhaustive-deps [isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue] ); const handleDeleteIssue = async () => { if (!handleDragDrop || !dragState.draggedIssueId) return; await handleDragDrop( dragState.source, dragState.destination, workspaceSlug?.toString(), projectId?.toString(), sub_group_by, group_by, issueMap, issueIds, updateIssue, removeIssue ).finally(() => { const draggedIssue = issueMap[dragState.draggedIssueId!]; removeIssue(draggedIssue.project_id, draggedIssue.id); setDeleteIssueModal(false); setDragState({}); captureIssueEvent({ eventName: ISSUE_DELETED, payload: { id: dragState.draggedIssueId!, state: "FAILED", element: "Kanban layout drag & drop" }, path: router.asPath, }); }); }; const handleKanbanFilters = (toggle: "group_by" | "sub_group_by", value: string) => { if (workspaceSlug && projectId) { let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || []; if (kanbanFilters.includes(value)) kanbanFilters = kanbanFilters.filter((_value) => _value != value); else kanbanFilters.push(value); updateFilters(projectId.toString(), EIssueFilterType.KANBAN_FILTERS, { [toggle]: kanbanFilters, }); } }; const kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters || { group_by: [], sub_group_by: [] }; return ( <> setDeleteIssueModal(false)} onSubmit={handleDeleteIssue} /> {showLoader && issues?.loader === "init-loader" && (
)}
{/* drag and delete component */}
{(provided, snapshot) => (
Drop here to delete the issue.
)}
); });