fix: drag and drop implementation in calendar layout and kanban layout (#2921)

* fix profile issue filters and kanban

* chore: calendar drag and drop

* chore: kanban drag and drop

* dev: remove issue from the kanban layout and resolved build errors

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
guru_sainath 2023-11-28 19:17:38 +05:30 committed by sriram veeraghanta
parent e16e468b8f
commit 011db50da6
18 changed files with 480 additions and 50 deletions

View File

@ -37,10 +37,12 @@ interface IBaseCalendarRoot {
[EIssueActions.REMOVE]?: (issue: IIssue) => void; [EIssueActions.REMOVE]?: (issue: IIssue) => void;
}; };
viewId?: string; viewId?: string;
handleDragDrop: (source: any, destination: any, issues: any, issueWithIds: any) => void;
} }
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
const { issueStore, issuesFilterStore, calendarViewStore, QuickActions, issueActions, viewId } = props; const { issueStore, issuesFilterStore, calendarViewStore, QuickActions, issueActions, viewId, handleDragDrop } =
props;
const displayFilters = issuesFilterStore.issueFilters?.displayFilters; const displayFilters = issuesFilterStore.issueFilters?.displayFilters;
@ -56,7 +58,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
// return if dropped on the same date // return if dropped on the same date
if (result.destination.droppableId === result.source.droppableId) return; if (result.destination.droppableId === result.source.droppableId) return;
calendarViewStore?.handleDragDrop(result.source, result.destination); if (handleDragDrop) handleDragDrop(result.source, result.destination, issues, groupedIssueIds);
}; };
const handleIssues = useCallback( const handleIssues = useCallback(

View File

@ -14,10 +14,15 @@ export const CycleCalendarLayout: React.FC = observer(() => {
cycleIssues: cycleIssueStore, cycleIssues: cycleIssueStore,
cycleIssuesFilter: cycleIssueFilterStore, cycleIssuesFilter: cycleIssueFilterStore,
cycleIssueCalendarView: cycleIssueCalendarViewStore, cycleIssueCalendarView: cycleIssueCalendarViewStore,
calendarHelpers: calendarHelperStore,
} = useMobxStore(); } = useMobxStore();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
@ -35,6 +40,20 @@ export const CycleCalendarLayout: React.FC = observer(() => {
}, },
}; };
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
cycleIssueStore,
issues,
issueWithIds,
cycleId
);
};
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={cycleIssueStore} issueStore={cycleIssueStore}
@ -43,6 +62,7 @@ export const CycleCalendarLayout: React.FC = observer(() => {
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}
viewId={cycleId} viewId={cycleId}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -14,10 +14,15 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
moduleIssues: moduleIssueStore, moduleIssues: moduleIssueStore,
moduleIssuesFilter: moduleIssueFilterStore, moduleIssuesFilter: moduleIssueFilterStore,
moduleIssueCalendarView: moduleIssueCalendarViewStore, moduleIssueCalendarView: moduleIssueCalendarViewStore,
calendarHelpers: calendarHelperStore,
} = useMobxStore(); } = useMobxStore();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string }; const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: (issue: IIssue) => { [EIssueActions.UPDATE]: (issue: IIssue) => {
@ -34,6 +39,20 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
}, },
}; };
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
moduleIssueStore,
issues,
issueWithIds,
moduleId
);
};
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={moduleIssueStore} issueStore={moduleIssueStore}
@ -42,6 +61,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}
viewId={moduleId} viewId={moduleId}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -10,27 +10,41 @@ import { useRouter } from "next/router";
export const CalendarLayout: React.FC = observer(() => { export const CalendarLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { const {
projectIssues: issueStore, projectIssues: issueStore,
issueCalendarView: issueCalendarViewStore, issueCalendarView: issueCalendarViewStore,
projectIssuesFilter: projectIssueFiltersStore, projectIssuesFilter: projectIssueFiltersStore,
calendarHelpers: calendarHelperStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id); issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
}, },
}; };
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
issueStore,
issues,
issueWithIds
);
};
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={issueStore} issueStore={issueStore}
@ -38,6 +52,7 @@ export const CalendarLayout: React.FC = observer(() => {
calendarViewStore={issueCalendarViewStore} calendarViewStore={issueCalendarViewStore}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -14,10 +14,11 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
viewIssues: projectViewIssuesStore, viewIssues: projectViewIssuesStore,
viewIssuesFilter: projectIssueViewFiltersStore, viewIssuesFilter: projectIssueViewFiltersStore,
projectViewIssueCalendarView: projectViewIssueCalendarViewStore, projectViewIssueCalendarView: projectViewIssueCalendarViewStore,
calendarHelpers: calendarHelperStore,
} = useMobxStore(); } = useMobxStore();
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
@ -32,6 +33,19 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
}, },
}; };
const handleDragDrop = (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
if (calendarHelperStore.handleDragDrop)
calendarHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
projectViewIssuesStore,
issues,
issueWithIds
);
};
return ( return (
<BaseCalendarRoot <BaseCalendarRoot
issueStore={projectViewIssuesStore} issueStore={projectViewIssuesStore}
@ -39,6 +53,7 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
calendarViewStore={projectViewIssueCalendarViewStore} calendarViewStore={projectViewIssueCalendarViewStore}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
issueActions={issueActions} issueActions={issueActions}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -1,5 +1,5 @@
import { FC, useCallback, useState } from "react"; import { FC, useCallback, useState } from "react";
import { DragDropContext } from "@hello-pangea/dnd"; import { DragDropContext, Droppable } from "@hello-pangea/dnd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
@ -29,6 +29,7 @@ import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
import { KanBan } from "./default"; import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes"; import { KanBanSwimLanes } from "./swimlanes";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
export interface IBaseKanBanLayout { export interface IBaseKanBanLayout {
issueStore: issueStore:
@ -54,6 +55,14 @@ export interface IBaseKanBanLayout {
showLoader?: boolean; showLoader?: boolean;
viewId?: string; viewId?: string;
currentStore?: EProjectStore; currentStore?: EProjectStore;
handleDragDrop?: (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: any,
issueWithIds: any
) => void;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>; addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
} }
@ -67,6 +76,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
showLoader, showLoader,
viewId, viewId,
currentStore, currentStore,
handleDragDrop,
addIssuesToView, addIssuesToView,
} = props; } = props;
@ -93,9 +103,9 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const currentKanBanView: "swimlanes" | "default" = sub_group_by ? "swimlanes" : "default"; const currentKanBanView: "swimlanes" | "default" = sub_group_by ? "swimlanes" : "default";
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {}; const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {};
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
const onDragStart = () => { const onDragStart = () => {
setIsDragStarted(true); setIsDragStarted(true);
}; };
@ -115,9 +125,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
) )
return; return;
currentKanBanView === "default" if (handleDragDrop) handleDragDrop(result.source, result.destination, sub_group_by, group_by, issues, issueIds);
? kanbanViewStore?.handleDragDrop(result.source, result.destination)
: kanbanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
}; };
const handleIssues = useCallback( const handleIssues = useCallback(
@ -147,6 +155,24 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}> <div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}> <DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
<div className={`fixed left-1/2 -translate-x-1/2 z-40 w-72 top-3 flex items-center justify-center mx-3`}>
<Droppable droppableId="issue-trash-box" isDropDisabled={!isDragStarted}>
{(provided, snapshot) => (
<div
className={`${
isDragStarted ? `opacity-100` : `opacity-0`
} w-full flex items-center justify-center rounded border-2 border-red-500/20 bg-custom-background-100 px-3 py-5 text-xs font-medium italic text-red-500 ${
snapshot.isDraggingOver ? "bg-red-500 blur-2xl opacity-70" : ""
} transition duration-300`}
ref={provided.innerRef}
{...provided.droppableProps}
>
Drop here to delete the issue.
</div>
)}
</Droppable>
</div>
{currentKanBanView === "default" ? ( {currentKanBanView === "default" ? (
<KanBan <KanBan
issues={issues} issues={issues}

View File

@ -16,13 +16,18 @@ export interface ICycleKanBanLayout {}
export const CycleKanBanLayout: React.FC = observer(() => { export const CycleKanBanLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
// store // store
const { const {
cycleIssues: cycleIssueStore, cycleIssues: cycleIssueStore,
cycleIssuesFilter: cycleIssueFilterStore, cycleIssuesFilter: cycleIssueFilterStore,
cycleIssueKanBanView: cycleIssueKanBanViewStore, cycleIssueKanBanView: cycleIssueKanBanViewStore,
kanBanHelpers: kanBanHelperStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
@ -40,6 +45,30 @@ export const CycleKanBanLayout: React.FC = observer(() => {
cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id); cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id);
}, },
}; };
const handleDragDrop = (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: IIssue[],
issueWithIds: any
) => {
if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
cycleIssueStore,
subGroupBy,
groupBy,
issues,
issueWithIds,
cycleId
);
};
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
@ -50,6 +79,7 @@ export const CycleKanBanLayout: React.FC = observer(() => {
QuickActions={CycleIssueQuickActions} QuickActions={CycleIssueQuickActions}
viewId={cycleId} viewId={cycleId}
currentStore={EProjectStore.CYCLE} currentStore={EProjectStore.CYCLE}
handleDragDrop={handleDragDrop}
addIssuesToView={(issues: string[]) => cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, issues)} addIssuesToView={(issues: string[]) => cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, issues)}
/> />
); );

View File

@ -16,13 +16,18 @@ export interface IModuleKanBanLayout {}
export const ModuleKanBanLayout: React.FC = observer(() => { export const ModuleKanBanLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string }; const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
// store // store
const { const {
moduleIssues: moduleIssueStore, moduleIssues: moduleIssueStore,
moduleIssuesFilter: moduleIssueFilterStore, moduleIssuesFilter: moduleIssueFilterStore,
moduleIssueKanBanView: moduleIssueKanBanViewStore, moduleIssueKanBanView: moduleIssueKanBanViewStore,
kanBanHelpers: kanBanHelperStore,
} = useMobxStore(); } = useMobxStore();
// const handleIssues = useCallback( // const handleIssues = useCallback(
@ -62,6 +67,29 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id); moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
}, },
}; };
const handleDragDrop = (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: IIssue[],
issueWithIds: any
) => {
if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
moduleIssueStore,
subGroupBy,
groupBy,
issues,
issueWithIds,
moduleId
);
};
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
@ -72,6 +100,7 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
QuickActions={ModuleIssueQuickActions} QuickActions={ModuleIssueQuickActions}
viewId={moduleId} viewId={moduleId}
currentStore={EProjectStore.MODULE} currentStore={EProjectStore.MODULE}
handleDragDrop={handleDragDrop}
addIssuesToView={(issues: string[]) => moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, issues)} addIssuesToView={(issues: string[]) => moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, issues)}
/> />
); );

View File

@ -15,12 +15,13 @@ export interface IKanBanLayout {}
export const KanBanLayout: React.FC = observer(() => { export const KanBanLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { const {
projectIssues: issueStore, projectIssues: issueStore,
projectIssuesFilter: issuesFilterStore, projectIssuesFilter: issuesFilterStore,
issueKanBanView: issueKanBanViewStore, issueKanBanView: issueKanBanViewStore,
kanBanHelpers: kanBanHelperStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
@ -36,6 +37,28 @@ export const KanBanLayout: React.FC = observer(() => {
}, },
}; };
const handleDragDrop = (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: IIssue[],
issueWithIds: any
) => {
if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
issueStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
};
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
@ -45,6 +68,7 @@ export const KanBanLayout: React.FC = observer(() => {
showLoader={true} showLoader={true}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
currentStore={EProjectStore.PROJECT} currentStore={EProjectStore.PROJECT}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -15,12 +15,13 @@ export interface IViewKanBanLayout {}
export const ProjectViewKanBanLayout: React.FC = observer(() => { export const ProjectViewKanBanLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query as { workspaceSlug: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { const {
viewIssues: projectViewIssuesStore, viewIssues: projectViewIssuesStore,
viewIssuesFilter: projectIssueViewFiltersStore, viewIssuesFilter: projectIssueViewFiltersStore,
issueKanBanView: projectViewIssueKanBanViewStore, issueKanBanView: projectViewIssueKanBanViewStore,
kanBanHelpers: kanBanHelperStore,
} = useMobxStore(); } = useMobxStore();
const issueActions = { const issueActions = {
@ -36,6 +37,28 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
}, },
}; };
const handleDragDrop = (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: IIssue[],
issueWithIds: any
) => {
if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
projectViewIssuesStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
};
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
@ -45,6 +68,7 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
showLoader={true} showLoader={true}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
currentStore={EProjectStore.PROJECT_VIEW} currentStore={EProjectStore.PROJECT_VIEW}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -23,9 +23,9 @@
"@nivo/line": "0.80.0", "@nivo/line": "0.80.0",
"@nivo/pie": "0.80.0", "@nivo/pie": "0.80.0",
"@nivo/scatterplot": "0.80.0", "@nivo/scatterplot": "0.80.0",
"@plane/document-editor": "*",
"@plane/lite-text-editor": "*", "@plane/lite-text-editor": "*",
"@plane/rich-text-editor": "*", "@plane/rich-text-editor": "*",
"@plane/document-editor": "*",
"@plane/ui": "*", "@plane/ui": "*",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@sentry/nextjs": "^7.36.0", "@sentry/nextjs": "^7.36.0",
@ -58,8 +58,8 @@
"sharp": "^0.32.1", "sharp": "^0.32.1",
"swr": "^2.1.3", "swr": "^2.1.3",
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",
"uuid": "^9.0.0", "use-debounce": "^9.0.4",
"use-debounce": "^9.0.4" "uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",

View File

@ -0,0 +1,53 @@
export interface ICalendarHelpers {
// actions
handleDragDrop: (
source: any,
destination: any,
workspaceSlug: string,
projectId: string,
store: any,
issues: any,
issueWithIds: any,
viewId?: string | null
) => void;
}
export class CalendarHelpers implements ICalendarHelpers {
constructor() {}
handleDragDrop = async (
source: any,
destination: any,
workspaceSlug: string,
projectId: string,
store: any,
issues: any,
issueWithIds: any,
viewId: string | null = null // it can be moduleId, cycleId
) => {
if (issues && issueWithIds) {
const sourceColumnId = source?.droppableId || null;
const destinationColumnId = destination?.droppableId || null;
if (!workspaceSlug || !projectId || !sourceColumnId || !destinationColumnId) return;
if (sourceColumnId === destinationColumnId) return;
// horizontal
if (sourceColumnId != destinationColumnId) {
const sourceIssues = issueWithIds[sourceColumnId] || [];
const [removed] = sourceIssues.splice(source.index, 1);
const removedIssueDetail = issues[removed];
const updateIssue = {
id: removedIssueDetail?.id,
target_date: destinationColumnId,
};
if (viewId) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
}
}
};
}

View File

@ -0,0 +1,160 @@
export interface IKanBanHelpers {
// actions
handleDragDrop: (
source: any,
destination: any,
workspaceSlug: string,
projectId: string,
store: any,
subGroupBy: string | null,
groupBy: string | null,
issues: any,
issueWithIds: any,
viewId?: string | null
) => void;
}
export class KanBanHelpers implements IKanBanHelpers {
constructor() {}
handleSortOrder = (destinationIssues: any, destinationIndex: any, issues: any) => {
const sortOrderDefaultValue = 65535;
let currentIssueState = {};
if (destinationIssues && destinationIssues.length > 0) {
if (destinationIndex === 0) {
const destinationIssueId = destinationIssues[destinationIndex];
currentIssueState = {
...currentIssueState,
sort_order: issues[destinationIssueId].sort_order - sortOrderDefaultValue,
};
} else if (destinationIndex === destinationIssues.length) {
const destinationIssueId = destinationIssues[destinationIndex - 1];
currentIssueState = {
...currentIssueState,
sort_order: issues[destinationIssueId].sort_order + sortOrderDefaultValue,
};
} else {
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
const destinationBottomIssueId = destinationIssues[destinationIndex];
currentIssueState = {
...currentIssueState,
sort_order: (issues[destinationTopIssueId].sort_order + issues[destinationBottomIssueId].sort_order) / 2,
};
}
} else {
currentIssueState = {
...currentIssueState,
sort_order: sortOrderDefaultValue,
};
}
return currentIssueState;
};
handleDragDrop = async (
source: any,
destination: any,
workspaceSlug: string,
projectId: string, // projectId for all views or user id in profile issues
store: any,
subGroupBy: string | null,
groupBy: string | null,
issues: any,
issueWithIds: any,
viewId: string | null = null // it can be moduleId, cycleId
) => {
if (issues && issueWithIds) {
let updateIssue: any = {};
const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
const sourceGroupByColumnId = sourceColumnId[0] || null;
const destinationGroupByColumnId = destinationColumnId[0] || null;
const sourceSubGroupByColumnId = sourceColumnId[1] || null;
const destinationSubGroupByColumnId = destinationColumnId[1] || null;
if (!workspaceSlug || !projectId || !groupBy || !sourceGroupByColumnId || !destinationGroupByColumnId) return;
if (destinationGroupByColumnId === "issue-trash-box") {
const sourceIssues = subGroupBy
? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
: issueWithIds[sourceGroupByColumnId];
const [removed] = sourceIssues.splice(source.index, 1);
console.log("removed", removed);
if (removed) {
if (viewId) store?.removeIssue(workspaceSlug, projectId, removed, viewId);
else store?.removeIssue(workspaceSlug, projectId, removed);
}
} else {
const sourceIssues = subGroupBy
? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
: issueWithIds[sourceGroupByColumnId];
const destinationIssues = subGroupBy
? issueWithIds[sourceSubGroupByColumnId][destinationGroupByColumnId]
: issueWithIds[destinationGroupByColumnId];
const [removed] = sourceIssues.splice(source.index, 1);
const removedIssueDetail = issues[removed];
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
updateIssue = {
id: removedIssueDetail?.id,
};
// for both horizontal and vertical dnd
updateIssue = {
...updateIssue,
...this.handleSortOrder(destinationIssues, destination.index, issues),
};
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
if (sourceGroupByColumnId != destinationGroupByColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
}
} else {
if (subGroupBy === "state")
updateIssue = {
...updateIssue,
state: destinationSubGroupByColumnId,
priority: destinationGroupByColumnId,
};
if (subGroupBy === "priority")
updateIssue = {
...updateIssue,
state: destinationGroupByColumnId,
priority: destinationSubGroupByColumnId,
};
}
} else {
updateIssue = {
id: removedIssueDetail?.id,
};
// for both horizontal and vertical dnd
updateIssue = {
...updateIssue,
...this.handleSortOrder(destinationIssues, destination.index, issues),
};
// for horizontal dnd
if (sourceColumnId != destinationColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
}
}
if (updateIssue && updateIssue?.id) {
if (viewId) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
}
}
}
};
}

View File

@ -1,5 +1,9 @@
/** project issues and issue-filters starts */ /** project issues and issue-filters starts */
// helpers
export * from "./base-issue-calendar-helper.store";
export * from "./base-issue-kanban-helper.store";
// issue and filter helpers // issue and filter helpers
export * from "./project-issues/base-issue.store"; export * from "./project-issues/base-issue.store";
export * from "./project-issues/base-issue-filter.store"; export * from "./project-issues/base-issue-filter.store";

View File

@ -1,11 +1,11 @@
import { action, makeObservable, observable, runInAction } from "mobx"; import { action, makeObservable, observable, runInAction } from "mobx";
import isEmpty from "lodash/isEmpty";
// types // types
import { RootStore } from "store/root"; import { RootStore } from "store/root";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types";
import { EFilterType } from "store/issues/types"; import { EFilterType } from "store/issues/types";
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store";
import isEmpty from "lodash/isEmpty";
interface IProjectIssuesFiltersOptions { interface IProjectIssuesFiltersOptions {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
@ -199,14 +199,14 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
try { try {
const displayProperties: IIssueDisplayProperties = { const displayProperties: IIssueDisplayProperties = {
assignee: true, assignee: true,
start_date: false, start_date: true,
due_date: false, due_date: true,
labels: true, labels: true,
key: true, key: true,
priority: true, priority: true,
state: false, state: false,
sub_issue_count: false, sub_issue_count: true,
link: false, link: true,
attachment_count: false, attachment_count: false,
estimate: false, estimate: false,
created_on: false, created_on: false,

View File

@ -1,4 +1,8 @@
import _ from "lodash"; import sortBy from "lodash/sortBy";
import get from "lodash/get";
import indexOf from "lodash/indexOf";
import reverse from "lodash/reverse";
import values from "lodash/values";
// types // types
import { IIssue, TIssueGroupByOptions, TIssueOrderByOptions } from "types"; import { IIssue, TIssueGroupByOptions, TIssueOrderByOptions } from "types";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
@ -51,7 +55,7 @@ export class IssueBaseStore implements IIssueBaseStore {
for (const issue in projectIssues) { for (const issue in projectIssues) {
const _issue = projectIssues[issue]; const _issue = projectIssues[issue];
const groupArray = this.getGroupArray(_.get(_issue, groupBy as keyof IIssue), isCalendarIssues); const groupArray = this.getGroupArray(get(_issue, groupBy as keyof IIssue), isCalendarIssues);
for (const group of groupArray) { for (const group of groupArray) {
if (group && _issues[group]) _issues[group].push(_issue.id); if (group && _issues[group]) _issues[group].push(_issue.id);
@ -82,8 +86,8 @@ export class IssueBaseStore implements IIssueBaseStore {
for (const issue in projectIssues) { for (const issue in projectIssues) {
const _issue = projectIssues[issue]; const _issue = projectIssues[issue];
const subGroupArray = this.getGroupArray(_.get(_issue, subGroupBy as keyof IIssue)); const subGroupArray = this.getGroupArray(get(_issue, subGroupBy as keyof IIssue));
const groupArray = this.getGroupArray(_.get(_issue, groupBy as keyof IIssue)); const groupArray = this.getGroupArray(get(_issue, groupBy as keyof IIssue));
for (const subGroup of subGroupArray) { for (const subGroup of subGroupArray) {
for (const group of groupArray) { for (const group of groupArray) {
@ -121,22 +125,22 @@ export class IssueBaseStore implements IIssueBaseStore {
}; };
issuesSortWithOrderBy = (issueObject: IIssueResponse, key: Partial<TIssueOrderByOptions>): IIssue[] => { issuesSortWithOrderBy = (issueObject: IIssueResponse, key: Partial<TIssueOrderByOptions>): IIssue[] => {
let array = _.values(issueObject); let array = values(issueObject);
array = _.sortBy(array, "created_at"); array = sortBy(array, "created_at");
switch (key) { switch (key) {
case "sort_order": case "sort_order":
return _.sortBy(array, "sort_order"); return sortBy(array, "sort_order");
case "-created_at": case "-created_at":
return _.reverse(_.sortBy(array, "created_at")); return reverse(sortBy(array, "created_at"));
case "-updated_at": case "-updated_at":
return _.reverse(_.sortBy(array, "updated_at")); return reverse(sortBy(array, "updated_at"));
case "start_date": case "start_date":
return _.sortBy(array, "start_date"); return sortBy(array, "start_date");
case "target_date": case "target_date":
return _.sortBy(array, "target_date"); return sortBy(array, "target_date");
case "priority": { case "priority": {
const sortArray = ISSUE_PRIORITIES.map((i) => i.key); const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
return _.sortBy(array, (_issue: IIssue) => _.indexOf(sortArray, _issue.priority)); return sortBy(array, (_issue: IIssue) => indexOf(sortArray, _issue.priority));
} }
default: default:
return array; return array;

View File

@ -94,24 +94,15 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined;
if (layout === "list" && orderBy) { if (layout === "list" && orderBy) {
console.log("list");
if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]); if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]);
else issues = this.unGroupedIssues(orderBy, this.issues[projectId]); else issues = this.unGroupedIssues(orderBy, this.issues[projectId]);
} else if (layout === "kanban" && groupBy && orderBy) { } else if (layout === "kanban" && groupBy && orderBy) {
console.log("kanban");
if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, this.issues[projectId]); if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, this.issues[projectId]);
else issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]); else issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]);
console.log("issues", issues); } else if (layout === "calendar")
} else if (layout === "calendar") {
console.log("calendar");
issues = this.groupedIssues("target_date" as TIssueGroupByOptions, "target_date", this.issues[projectId], true); issues = this.groupedIssues("target_date" as TIssueGroupByOptions, "target_date", this.issues[projectId], true);
} else if (layout === "spreadsheet") { else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[projectId]);
console.log("spreadsheet"); else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", this.issues[projectId]);
issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[projectId]);
} else if (layout === "gantt_chart") {
console.log("gantt_chart");
issues = this.unGroupedIssues(orderBy ?? "sort_order", this.issues[projectId]);
}
return issues; return issues;
} }

View File

@ -164,6 +164,11 @@ import {
// global issues filter // global issues filter
IGlobalIssuesFilterStore, IGlobalIssuesFilterStore,
GlobalIssuesFilterStore, GlobalIssuesFilterStore,
// helpers
ICalendarHelpers,
CalendarHelpers,
IKanBanHelpers,
KanBanHelpers,
} from "store/issues"; } from "store/issues";
import { CycleIssueFiltersStore, ICycleIssueFiltersStore } from "store/cycle-issues"; import { CycleIssueFiltersStore, ICycleIssueFiltersStore } from "store/cycle-issues";
@ -274,6 +279,10 @@ export class RootStore {
workspaceGlobalIssues: IGlobalIssuesStore; workspaceGlobalIssues: IGlobalIssuesStore;
workspaceGlobalIssuesFilter: IGlobalIssuesFilterStore; workspaceGlobalIssuesFilter: IGlobalIssuesFilterStore;
calendarHelpers: ICalendarHelpers;
kanBanHelpers: IKanBanHelpers;
// project v3 issue and issue-filters ends // project v3 issue and issue-filters ends
cycleIssueFilters: ICycleIssueFiltersStore; cycleIssueFilters: ICycleIssueFiltersStore;
@ -378,6 +387,10 @@ export class RootStore {
this.workspaceGlobalIssues = new GlobalIssuesStore(this); this.workspaceGlobalIssues = new GlobalIssuesStore(this);
this.workspaceGlobalIssuesFilter = new GlobalIssuesFilterStore(this); this.workspaceGlobalIssuesFilter = new GlobalIssuesFilterStore(this);
this.calendarHelpers = new CalendarHelpers();
this.kanBanHelpers = new KanBanHelpers();
// project v3 issue and issue-filters ends // project v3 issue and issue-filters ends
this.cycleIssueFilters = new CycleIssueFiltersStore(this); this.cycleIssueFilters = new CycleIssueFiltersStore(this);