import { FC, useCallback, useRef, useState } from "react"; import { DragDropContext, DragStart, DraggableLocation, DropResult, Droppable } from "@hello-pangea/dnd"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; // hooks import { useEventTracker, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { Spinner } from "@plane/ui"; // types import { TIssue } from "@plane/types"; import { EIssueActions } from "../types"; import { IQuickActionProps } from "../list/list-view-types"; import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project"; //components import { KanBan } from "./default"; import { KanBanSwimLanes } from "./swimlanes"; import { DeleteIssueModal } from "components/issues"; import { EUserProjectRoles } from "constants/project"; import { useIssues } from "hooks/store/use-issues"; import { handleDragDrop } from "./utils"; import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle"; import { IDraftIssues, IDraftIssuesFilter } from "store/issue/draft"; import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile"; import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module"; import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views"; import { EIssueFilterType, TCreateModalStoreTypes } from "constants/issue"; import { ISSUE_DELETED } from "constants/event-tracker"; export interface IBaseKanBanLayout { issues: IProjectIssues | ICycleIssues | IDraftIssues | IModuleIssues | IProjectViewIssues | IProfileIssues; issuesFilter: | IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IDraftIssuesFilter | IProjectViewIssuesFilter | IProfileIssuesFilter; QuickActions: FC; issueActions: { [EIssueActions.DELETE]: (issue: TIssue) => Promise; [EIssueActions.UPDATE]?: (issue: TIssue) => Promise; [EIssueActions.REMOVE]?: (issue: TIssue) => Promise; }; showLoader?: boolean; viewId?: string; storeType?: TCreateModalStoreTypes; 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 { issues, issuesFilter, QuickActions, issueActions, 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 } = useIssues(); // toast alert const { setToastAlert } = useToast(); 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(), issues, sub_group_by, group_by, issueMap, issueIds, viewId ).catch((err) => { setToastAlert({ title: "Error", type: "error", message: err.detail ?? "Failed to perform this action", }); }); } } }; const handleIssues = useCallback( async (issue: TIssue, action: EIssueActions) => { if (issueActions[action]) { await issueActions[action]!(issue); } }, [issueActions] ); const renderQuickActions = useCallback( (issue: TIssue, customActionButton?: React.ReactElement) => ( handleIssues(issue, EIssueActions.DELETE)} handleUpdate={ issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined } handleRemoveFromView={ issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined } readOnly={!isEditingAllowed || isCompletedCycle} /> ), // eslint-disable-next-line react-hooks/exhaustive-deps [issueActions, handleIssues] ); const handleDeleteIssue = async () => { if (!handleDragDrop) return; await handleDragDrop( dragState.source, dragState.destination, workspaceSlug?.toString(), projectId?.toString(), issues, sub_group_by, group_by, issueMap, issueIds, viewId ).finally(() => { handleIssues(issueMap[dragState.draggedIssueId!], EIssueActions.DELETE); 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); issuesFilter.updateFilters( workspaceSlug.toString(), projectId.toString(), EIssueFilterType.KANBAN_FILTERS, { [toggle]: _kanbanFilters, }, viewId ); } }; 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.
)}
); });