forked from github/plane
dev: layout setup for draft issues
This commit is contained in:
parent
bc6a983d1e
commit
55b9fbffd7
@ -1,17 +1,13 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import { mutate } from "swr";
|
// store
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
|
|
||||||
// headless ui
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
// services
|
|
||||||
import { IssueDraftService } from "services/issue";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// icons
|
// icons
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
@ -19,31 +15,29 @@ import { AlertTriangle } from "lucide-react";
|
|||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import type { IIssue } from "types";
|
import type { IIssue } from "types";
|
||||||
// fetch-keys
|
|
||||||
import { PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
data: IIssue | null;
|
data: IIssue | null;
|
||||||
onSubmit?: () => Promise<void> | void;
|
onSuccess?: () => Promise<void> | void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const issueDraftService = new IssueDraftService();
|
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||||
|
const { isOpen, handleClose, data, onSuccess } = props;
|
||||||
|
|
||||||
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
// router
|
||||||
const { isOpen, handleClose, data, onSubmit } = props;
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
// store
|
||||||
|
const { draftIssues: draftIssueStore } = useMobxStore();
|
||||||
|
|
||||||
|
// states
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
|
|
||||||
const { params } = useIssuesView();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
@ -58,30 +52,28 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
if (!workspaceSlug || !data || !user) return;
|
if (!workspaceSlug || !data || !user) return;
|
||||||
|
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
await draftIssueStore
|
||||||
await issueDraftService
|
|
||||||
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setIsDeleteLoading(false);
|
|
||||||
handleClose();
|
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
message: "Draft Issue deleted successfully",
|
message: "Draft Issue deleted successfully",
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(() => {
|
||||||
console.log(error);
|
|
||||||
handleClose();
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
message: "Something went wrong",
|
message: "Something went wrong",
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
handleClose();
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
});
|
});
|
||||||
if (onSubmit) await onSubmit();
|
|
||||||
|
if (onSuccess) await onSuccess();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -146,4 +138,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -166,25 +166,6 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [JSON.stringify(payload), isOpen, data]);
|
}, [JSON.stringify(payload), isOpen, data]);
|
||||||
|
|
||||||
// const onClose = () => {
|
|
||||||
// handleClose();
|
|
||||||
// };
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen || data) return;
|
|
||||||
|
|
||||||
setLocalStorageValue(
|
|
||||||
JSON.stringify({
|
|
||||||
...payload,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [JSON.stringify(payload), isOpen, data]);
|
|
||||||
|
|
||||||
// const onClose = () => {
|
|
||||||
// handleClose();
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleCreateUpdateIssue = async (
|
const handleCreateUpdateIssue = async (
|
||||||
formData: Partial<IIssue>,
|
formData: Partial<IIssue>,
|
||||||
action: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue" = "createDraft"
|
action: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue" = "createDraft"
|
||||||
@ -193,7 +174,6 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
|
|||||||
{
|
{
|
||||||
...(data ?? {}),
|
...(data ?? {}),
|
||||||
...formData,
|
...formData,
|
||||||
is_draft: action === "createDraft" || action === "updateDraft",
|
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
);
|
);
|
||||||
|
@ -6,28 +6,22 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
// services
|
||||||
import { IssueService, IssueDraftService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
import { ModuleService } from "services/module.service";
|
import { ModuleService } from "services/module.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import useMyIssues from "hooks/my-issues/use-my-issues";
|
|
||||||
// components
|
// components
|
||||||
import { DraftIssueForm } from "components/issues";
|
import { DraftIssueForm } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import type { IIssue } from "types";
|
import type { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
PROJECT_ISSUES_DETAILS,
|
|
||||||
USER_ISSUE,
|
|
||||||
SUB_ISSUES,
|
SUB_ISSUES,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
VIEW_ISSUES,
|
|
||||||
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
CYCLE_DETAILS,
|
CYCLE_DETAILS,
|
||||||
MODULE_DETAILS,
|
MODULE_DETAILS,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
@ -57,7 +51,6 @@ interface IssuesModalProps {
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
const issueDraftService = new IssueDraftService();
|
|
||||||
const moduleService = new ModuleService();
|
const moduleService = new ModuleService();
|
||||||
|
|
||||||
export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
||||||
@ -65,7 +58,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
data,
|
data,
|
||||||
handleClose,
|
handleClose,
|
||||||
isOpen,
|
isOpen,
|
||||||
isUpdatingSingleIssue = false,
|
|
||||||
prePopulateData: prePopulateDataProps,
|
prePopulateData: prePopulateDataProps,
|
||||||
fieldsToShow = ["all"],
|
fieldsToShow = ["all"],
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -77,21 +70,23 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue> | undefined>(undefined);
|
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue> | undefined>(undefined);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
const { project: projectStore } = useMobxStore();
|
const {
|
||||||
|
project: projectStore,
|
||||||
|
draftIssues: draftIssueStore,
|
||||||
|
issueDetail: issueDetailStore,
|
||||||
|
issue: issueStore,
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||||
|
|
||||||
const { displayFilters, params } = useIssuesView();
|
const { params } = useIssuesView();
|
||||||
const { ...viewGanttParams } = params;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
|
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
|
||||||
|
|
||||||
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
@ -133,35 +128,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
}
|
}
|
||||||
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, user?.id]);
|
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, user?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPreloadedData(prePopulateDataProps ?? {});
|
|
||||||
|
|
||||||
if (cycleId && !prePopulateDataProps?.cycle) {
|
|
||||||
setPreloadedData((prevData) => ({
|
|
||||||
...(prevData ?? {}),
|
|
||||||
...prePopulateDataProps,
|
|
||||||
cycle: cycleId.toString(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (moduleId && !prePopulateDataProps?.module) {
|
|
||||||
setPreloadedData((prevData) => ({
|
|
||||||
...(prevData ?? {}),
|
|
||||||
...prePopulateDataProps,
|
|
||||||
module: moduleId.toString(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(router.asPath.includes("my-issues") || router.asPath.includes("assigned")) &&
|
|
||||||
!prePopulateDataProps?.assignees
|
|
||||||
) {
|
|
||||||
setPreloadedData((prevData) => ({
|
|
||||||
...(prevData ?? {}),
|
|
||||||
...prePopulateDataProps,
|
|
||||||
assignees: prePopulateDataProps?.assignees ?? [user?.id ?? ""],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, user?.id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if modal is closed, reset active project to null
|
// if modal is closed, reset active project to null
|
||||||
// and return to avoid activeProject being set to some other project
|
// and return to avoid activeProject being set to some other project
|
||||||
@ -184,32 +150,19 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||||
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "");
|
|
||||||
|
|
||||||
const createDraftIssue = async (payload: Partial<IIssue>) => {
|
const createDraftIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!workspaceSlug || !activeProject || !user) return;
|
if (!workspaceSlug || !activeProject || !user) return;
|
||||||
|
|
||||||
await issueDraftService
|
await draftIssueStore
|
||||||
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
|
.createDraftIssue(workspaceSlug.toString(), activeProject, payload)
|
||||||
.then(async () => {
|
.then(() => {
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
draftIssueStore.fetchIssues(workspaceSlug.toString(), activeProject);
|
||||||
|
|
||||||
if (groupedIssues) mutateMyIssues();
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (payload.assignees_list?.some((assignee) => assignee === user?.id))
|
|
||||||
mutate(USER_ISSUE(workspaceSlug as string));
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -223,26 +176,21 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateDraftIssue = async (payload: Partial<IIssue>) => {
|
const updateDraftIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!user) return;
|
if (!user || !workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
await issueDraftService
|
await draftIssueStore
|
||||||
.updateDraftIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
.updateDraftIssue(workspaceSlug.toString(), activeProject, payload as IIssue)
|
||||||
.then((res) => {
|
.then((response) => {
|
||||||
if (isUpdatingSingleIssue) {
|
if (!createMore) onClose();
|
||||||
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
|
||||||
} else {
|
// replace with actual group id and sub group id
|
||||||
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
draftIssueStore.updateIssueStructure(null, null, response);
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!payload.is_draft) {
|
if (!payload.is_draft) {
|
||||||
if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") addIssueToCycle(response.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module);
|
if (payload.module && payload.module !== "") addIssueToModule(response.id, payload.module);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!createMore) onClose();
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -261,6 +209,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
|
// TODO: switch to store
|
||||||
await issueService
|
await issueService
|
||||||
.addIssueToCycle(
|
.addIssueToCycle(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
@ -282,6 +231,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
|
// TODO: switch to store
|
||||||
await moduleService
|
await moduleService
|
||||||
.addIssuesToModule(
|
.addIssuesToModule(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
@ -303,31 +253,20 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
const createIssue = async (payload: Partial<IIssue>) => {
|
const createIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
await issueService
|
await issueDetailStore
|
||||||
.createIssue(workspaceSlug as string, activeProject ?? "", payload, user)
|
.createIssue(workspaceSlug.toString(), activeProject, payload)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
issueStore.fetchIssues(workspaceSlug.toString(), activeProject);
|
||||||
|
|
||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||||
|
|
||||||
if (displayFilters.layout === "gantt_chart")
|
|
||||||
mutate(ganttFetchKey, {
|
|
||||||
start_target_date: true,
|
|
||||||
order_by: "sort_order",
|
|
||||||
});
|
|
||||||
if (groupedIssues) mutateMyIssues();
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!createMore) onClose();
|
|
||||||
|
|
||||||
if (payload.assignees_list?.some((assignee) => assignee === user?.id))
|
|
||||||
mutate(USER_ISSUE(workspaceSlug as string));
|
|
||||||
|
|
||||||
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -337,6 +276,19 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
message: "Issue could not be created. Please try again.",
|
message: "Issue could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!createMore) onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertDraftToIssue = async (payload: Partial<IIssue>) => {
|
||||||
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
await draftIssueStore.convertDraftIssueToIssue(workspaceSlug.toString(), activeProject, payload?.id!).then(() => {
|
||||||
|
draftIssueStore.fetchIssues(workspaceSlug.toString(), activeProject);
|
||||||
|
|
||||||
|
// adding to cycle or/and module if payload is available for the same
|
||||||
|
if (payload.cycle && payload.cycle !== "") addIssueToCycle(payload?.id!, payload.cycle);
|
||||||
|
if (payload.module && payload.module !== "") addIssueToModule(payload?.id!, payload.module);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (
|
const handleFormSubmit = async (
|
||||||
@ -354,8 +306,9 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (action === "createDraft") await createDraftIssue(payload);
|
if (action === "createDraft") await createDraftIssue(payload);
|
||||||
else if (action === "updateDraft" || action === "convertToNewIssue") await updateDraftIssue(payload);
|
else if (action === "updateDraft") await updateDraftIssue(payload);
|
||||||
else if (action === "createNewIssue") await createIssue(payload);
|
else if (action === "createNewIssue") await createIssue(payload);
|
||||||
|
else if (action === "convertToNewIssue") await convertDraftToIssue(payload);
|
||||||
|
|
||||||
clearDraftIssueLocalStorage();
|
clearDraftIssueLocalStorage();
|
||||||
|
|
||||||
|
@ -3,3 +3,4 @@ export * from "./module-root";
|
|||||||
export * from "./profile-issues-root";
|
export * from "./profile-issues-root";
|
||||||
export * from "./project-root";
|
export * from "./project-root";
|
||||||
export * from "./project-view-root";
|
export * from "./project-view-root";
|
||||||
|
export * from "./project-draft-issue-root";
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { DragDropContext } from "@hello-pangea/dnd";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// components
|
||||||
|
import { KanBanSwimLanes } from "../swimlanes";
|
||||||
|
import { KanBan } from "../default";
|
||||||
|
import { DraftIssueQuickActions } from "components/issues";
|
||||||
|
// helpers
|
||||||
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||||
|
|
||||||
|
export const DraftIssueKanBanLayout: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const {
|
||||||
|
project: projectStore,
|
||||||
|
issueFilter: issueFilterStore,
|
||||||
|
issueKanBanView: issueKanBanViewStore,
|
||||||
|
draftIssues: draftIssuesStore,
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
|
const issues = draftIssuesStore.getDraftIssues?.data;
|
||||||
|
|
||||||
|
const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null;
|
||||||
|
|
||||||
|
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||||
|
|
||||||
|
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||||
|
|
||||||
|
const currentKanBanView = "default";
|
||||||
|
// const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by
|
||||||
|
// ? "swimlanes"
|
||||||
|
// : "default";
|
||||||
|
|
||||||
|
const onDragEnd = (result: any) => {
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
result.destination &&
|
||||||
|
result.source &&
|
||||||
|
result.destination.droppableId === result.source.droppableId &&
|
||||||
|
result.destination.index === result.source.index
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: use draft issue store instead
|
||||||
|
currentKanBanView === "default"
|
||||||
|
? issueKanBanViewStore?.handleDragDrop(result.source, result.destination)
|
||||||
|
: issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIssues = useCallback(
|
||||||
|
(
|
||||||
|
sub_group_by: string | null,
|
||||||
|
group_by: string | null,
|
||||||
|
issue: IIssue,
|
||||||
|
action: "update" | "delete" | "convertToIssue"
|
||||||
|
) => {
|
||||||
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
if (action === "update") {
|
||||||
|
draftIssuesStore.updateIssueStructure(group_by, sub_group_by, issue);
|
||||||
|
draftIssuesStore.updateDraftIssue(workspaceSlug.toString(), issue.project, issue);
|
||||||
|
}
|
||||||
|
if (action === "delete") draftIssuesStore.deleteDraftIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||||
|
if (action === "convertToIssue")
|
||||||
|
draftIssuesStore.convertDraftIssueToIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||||
|
},
|
||||||
|
[draftIssuesStore, workspaceSlug]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||||
|
issueKanBanViewStore.handleKanBanToggle(toggle, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
|
||||||
|
|
||||||
|
const states = projectStore?.projectStates || null;
|
||||||
|
const priorities = ISSUE_PRIORITIES || null;
|
||||||
|
const labels = projectStore?.projectLabels || null;
|
||||||
|
const members = projectStore?.projectMembers || null;
|
||||||
|
const stateGroups = ISSUE_STATE_GROUPS || null;
|
||||||
|
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
||||||
|
const estimates =
|
||||||
|
projectDetails?.estimate !== null
|
||||||
|
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
{currentKanBanView === "default" ? (
|
||||||
|
<KanBan
|
||||||
|
issues={issues}
|
||||||
|
sub_group_by={sub_group_by}
|
||||||
|
group_by={group_by}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(sub_group_by, group_by, issue) => (
|
||||||
|
<DraftIssueQuickActions
|
||||||
|
issue={issue}
|
||||||
|
handleUpdate={(issue: any, action: any) => handleIssues(sub_group_by, group_by, issue, action)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
display_properties={display_properties}
|
||||||
|
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||||
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={labels}
|
||||||
|
members={members?.map((m) => m.member) ?? null}
|
||||||
|
projects={projects}
|
||||||
|
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<KanBanSwimLanes
|
||||||
|
issues={issues}
|
||||||
|
sub_group_by={sub_group_by}
|
||||||
|
group_by={group_by}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(sub_group_by, group_by, issue) => (
|
||||||
|
<DraftIssueQuickActions
|
||||||
|
issue={issue}
|
||||||
|
handleUpdate={(issue: any, action: any) => handleIssues(sub_group_by, group_by, issue, action)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
display_properties={display_properties}
|
||||||
|
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||||
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={labels}
|
||||||
|
members={members?.map((m) => m.member) ?? null}
|
||||||
|
projects={projects}
|
||||||
|
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -3,3 +3,4 @@ export * from "./module-root";
|
|||||||
export * from "./profile-issues-root";
|
export * from "./profile-issues-root";
|
||||||
export * from "./project-root";
|
export * from "./project-root";
|
||||||
export * from "./project-view-root";
|
export * from "./project-view-root";
|
||||||
|
export * from "./project-draft-issue-root";
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
import { FC, useCallback } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// hooks
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// components
|
||||||
|
import { List } from "../default";
|
||||||
|
import { DraftIssueQuickActions } from "components/issues";
|
||||||
|
// helpers
|
||||||
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||||
|
|
||||||
|
export const DraftIssueListLayout: FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { project: projectStore, draftIssues: draftIssuesStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
const issues = draftIssuesStore.getDraftIssues?.data;
|
||||||
|
|
||||||
|
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||||
|
|
||||||
|
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||||
|
|
||||||
|
const handleIssues = useCallback(
|
||||||
|
(group_by: string | null, issue: IIssue, action: "update" | "delete" | "convertToIssue") => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
if (action === "update") {
|
||||||
|
draftIssuesStore.updateDraftIssue(workspaceSlug.toString(), projectId.toString(), issue);
|
||||||
|
draftIssuesStore.updateIssueStructure(group_by, null, issue);
|
||||||
|
} else if (action === "delete") {
|
||||||
|
draftIssuesStore.deleteDraftIssue(workspaceSlug.toString(), projectId.toString(), issue.id);
|
||||||
|
} else if (action === "convertToIssue") {
|
||||||
|
draftIssuesStore.convertDraftIssueToIssue(workspaceSlug.toString(), projectId.toString(), issue.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, draftIssuesStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
|
||||||
|
|
||||||
|
const states = projectStore?.projectStates || null;
|
||||||
|
const priorities = ISSUE_PRIORITIES || null;
|
||||||
|
const labels = projectStore?.projectLabels || null;
|
||||||
|
const members = projectStore?.projectMembers || null;
|
||||||
|
const stateGroups = ISSUE_STATE_GROUPS || null;
|
||||||
|
const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null;
|
||||||
|
const estimates =
|
||||||
|
projectDetails?.estimate !== null
|
||||||
|
? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-full bg-custom-background-90">
|
||||||
|
<List
|
||||||
|
issues={issues}
|
||||||
|
group_by={group_by}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={(group_by, issue) => (
|
||||||
|
<DraftIssueQuickActions
|
||||||
|
issue={issue}
|
||||||
|
handleUpdate={(issue: any, action: any) => handleIssues(group_by, issue, action)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
display_properties={display_properties}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={labels}
|
||||||
|
members={members?.map((m) => m.member) ?? null}
|
||||||
|
projects={projects}
|
||||||
|
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,76 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { CustomMenu } from "@plane/ui";
|
||||||
|
import { Copy, Pencil, Trash2 } from "lucide-react";
|
||||||
|
// components
|
||||||
|
import { CreateUpdateDraftIssueModal, DeleteDraftIssueModal } from "components/issues";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
issue: IIssue;
|
||||||
|
handleUpdate: (data: IIssue, action: any) => Promise<void> | void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DraftIssueQuickActions: React.FC<Props> = (props) => {
|
||||||
|
const { issue, handleUpdate } = props;
|
||||||
|
|
||||||
|
// states
|
||||||
|
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||||
|
const [issueToEdit, setIssueToEdit] = useState<IIssue | null>(null);
|
||||||
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteDraftIssueModal data={issue} isOpen={deleteIssueModal} handleClose={() => setDeleteIssueModal(false)} />
|
||||||
|
<CreateUpdateDraftIssueModal
|
||||||
|
isOpen={createUpdateIssueModal}
|
||||||
|
handleClose={() => {
|
||||||
|
setCreateUpdateIssueModal(false);
|
||||||
|
setIssueToEdit(null);
|
||||||
|
}}
|
||||||
|
// pre-populate date only if not editing
|
||||||
|
prePopulateData={!issueToEdit && createUpdateIssueModal ? { ...issue } : {}}
|
||||||
|
data={issueToEdit}
|
||||||
|
/>
|
||||||
|
<CustomMenu ellipsis>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIssueToEdit(issue);
|
||||||
|
setCreateUpdateIssueModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Pencil className="h-3 w-3" />
|
||||||
|
Edit draft issue
|
||||||
|
</div>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleUpdate(issue, "convertToIssue");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
Convert to issue
|
||||||
|
</div>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setDeleteIssueModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
Delete draft issue
|
||||||
|
</div>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
</CustomMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
export * from "./cycle-issue";
|
export * from "./cycle-issue";
|
||||||
export * from "./module-issue";
|
export * from "./module-issue";
|
||||||
export * from "./project-issue";
|
export * from "./project-issue";
|
||||||
|
export * from "./draft-issue";
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import useSWR from "swr";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// components
|
||||||
|
import { DraftIssueListLayout, DraftIssueKanBanLayout, ProjectAppliedFiltersRoot } from "components/issues";
|
||||||
|
|
||||||
|
export const DraftIssueLayoutRoot: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { issueFilter: issueFilterStore, draftIssues: draftIssuesStore } = useMobxStore();
|
||||||
|
|
||||||
|
useSWR(workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||||
|
await draftIssuesStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||||
|
<ProjectAppliedFiltersRoot />
|
||||||
|
<div className="w-full h-full overflow-auto">
|
||||||
|
{activeLayout === "list" ? (
|
||||||
|
<DraftIssueListLayout />
|
||||||
|
) : activeLayout === "kanban" ? (
|
||||||
|
<DraftIssueKanBanLayout />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -3,3 +3,4 @@ export * from "./global-view-layout-root";
|
|||||||
export * from "./module-layout-root";
|
export * from "./module-layout-root";
|
||||||
export * from "./project-layout-root";
|
export * from "./project-layout-root";
|
||||||
export * from "./project-view-layout-root";
|
export * from "./project-view-layout-root";
|
||||||
|
export * from "./draft-issue-layout-root";
|
||||||
|
@ -5,8 +5,6 @@ import { mutate } from "swr";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
|
||||||
import { IssueDraftService } from "services/issue";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
@ -15,7 +13,7 @@ import { IssueForm, ConfirmIssueDiscard } from "components/issues";
|
|||||||
// types
|
// types
|
||||||
import type { IIssue } from "types";
|
import type { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys";
|
import { SUB_ISSUES } from "constants/fetch-keys";
|
||||||
|
|
||||||
export interface IssuesModalProps {
|
export interface IssuesModalProps {
|
||||||
data?: IIssue | null;
|
data?: IIssue | null;
|
||||||
@ -39,8 +37,6 @@ export interface IssuesModalProps {
|
|||||||
onSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
onSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const issueDraftService = new IssueDraftService();
|
|
||||||
|
|
||||||
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
||||||
const { data, handleClose, isOpen, prePopulateData: prePopulateDataProps, fieldsToShow = ["all"], onSubmit } = props;
|
const { data, handleClose, isOpen, prePopulateData: prePopulateDataProps, fieldsToShow = ["all"], onSubmit } = props;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
// contexts
|
// contexts
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||||
|
import { DraftIssueLayoutRoot } from "components/issues";
|
||||||
// ui
|
// ui
|
||||||
import { ProjectDraftIssueHeader } from "components/headers";
|
import { ProjectDraftIssueHeader } from "components/headers";
|
||||||
// icons
|
// icons
|
||||||
@ -30,6 +31,10 @@ const ProjectDraftIssues: NextPage = () => {
|
|||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="h-full w-full">
|
||||||
|
<DraftIssueLayoutRoot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</IssueViewContextProvider>
|
</IssueViewContextProvider>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from "./issue.store";
|
export * from "./issue.store";
|
||||||
export * from "./issue_filters.store";
|
export * from "./issue_filters.store";
|
||||||
|
export * from "./issue_kanban_view.store";
|
||||||
|
@ -23,14 +23,23 @@ export interface IIssueDraftStore {
|
|||||||
draftIssues: {
|
draftIssues: {
|
||||||
[project_id: string]: {
|
[project_id: string]: {
|
||||||
grouped: {
|
grouped: {
|
||||||
[group_id: string]: IIssue[];
|
[group_id: string]: {
|
||||||
|
data: IIssue[];
|
||||||
|
total_issues: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
groupWithSubGroups: {
|
groupWithSubGroups: {
|
||||||
[group_id: string]: {
|
[group_id: string]: {
|
||||||
[sub_group_id: string]: IIssue[];
|
[sub_group_id: string]: {
|
||||||
|
data: IIssue[];
|
||||||
|
total_issues: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
ungrouped: IIssue[];
|
};
|
||||||
|
ungrouped: {
|
||||||
|
data: IIssue[];
|
||||||
|
total_issues: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
@ -43,9 +52,9 @@ export interface IIssueDraftStore {
|
|||||||
fetchIssues: (workspaceSlug: string, projectId: string) => Promise<any>;
|
fetchIssues: (workspaceSlug: string, projectId: string) => Promise<any>;
|
||||||
createDraftIssue: (workspaceSlug: string, projectId: string, issueForm: Partial<IIssue>) => Promise<any>;
|
createDraftIssue: (workspaceSlug: string, projectId: string, issueForm: Partial<IIssue>) => Promise<any>;
|
||||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void;
|
||||||
deleteDraftIssue: (workspaceSlug: string, projectId: string, issueId: string) => void;
|
deleteDraftIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
||||||
updateDraftIssue: (workspaceSlug: string, projectId: string, issueForm: Partial<IIssue>) => void;
|
updateDraftIssue: (workspaceSlug: string, projectId: string, issueForm: Partial<IIssue>) => Promise<any>;
|
||||||
convertDraftIssueToIssue: (workspaceSlug: string, projectId: string, issueId: string) => void;
|
convertDraftIssueToIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<any>;
|
||||||
|
|
||||||
// service
|
// service
|
||||||
draftIssueService: IssueDraftService;
|
draftIssueService: IssueDraftService;
|
||||||
@ -82,7 +91,8 @@ export class IssueDraftStore implements IIssueDraftStore {
|
|||||||
|
|
||||||
get getIssueType() {
|
get getIssueType() {
|
||||||
// FIXME: this is temporary for development
|
// FIXME: this is temporary for development
|
||||||
return "ungrouped";
|
return "grouped";
|
||||||
|
// return "ungrouped";
|
||||||
|
|
||||||
const groupedLayouts = ["kanban", "list", "calendar"];
|
const groupedLayouts = ["kanban", "list", "calendar"];
|
||||||
const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
|
const ungroupedLayouts = ["spreadsheet", "gantt_chart"];
|
||||||
@ -120,7 +130,9 @@ export class IssueDraftStore implements IIssueDraftStore {
|
|||||||
|
|
||||||
// const params = this.rootStore?.issueFilter?.appliedFilters;
|
// const params = this.rootStore?.issueFilter?.appliedFilters;
|
||||||
// TODO: use actual params using applied filters
|
// TODO: use actual params using applied filters
|
||||||
const params = {};
|
const params = {
|
||||||
|
group_by: "state",
|
||||||
|
};
|
||||||
const issueResponse = await this.draftIssueService.getDraftIssues(workspaceSlug, projectId, params);
|
const issueResponse = await this.draftIssueService.getDraftIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
const issueType = this.getIssueType;
|
const issueType = this.getIssueType;
|
||||||
@ -186,24 +198,47 @@ export class IssueDraftStore implements IIssueDraftStore {
|
|||||||
|
|
||||||
if (issueType === "grouped" && group_id) {
|
if (issueType === "grouped" && group_id) {
|
||||||
issues = issues as IIssueGroupedStructure;
|
issues = issues as IIssueGroupedStructure;
|
||||||
|
const currentIssue = issues?.[group_id]?.find((i: IIssue) => i?.id === issue?.id);
|
||||||
|
|
||||||
|
// if issue is already present in the list then update it
|
||||||
|
if (currentIssue)
|
||||||
issues = {
|
issues = {
|
||||||
...issues,
|
...issues,
|
||||||
[group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
[group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
||||||
};
|
};
|
||||||
|
// if issue is not present in the list then append it
|
||||||
|
else issues = { ...issues, [group_id]: [...issues[group_id], issue] };
|
||||||
}
|
}
|
||||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
||||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
issues = issues as IIssueGroupWithSubGroupsStructure;
|
||||||
|
const currentIssue = issues?.[sub_group_id]?.[group_id]?.find((i: IIssue) => i?.id === issue?.id);
|
||||||
|
|
||||||
|
// if issue is already present in the list then update it
|
||||||
|
if (currentIssue)
|
||||||
issues = {
|
issues = {
|
||||||
...issues,
|
...issues,
|
||||||
[sub_group_id]: {
|
[sub_group_id]: {
|
||||||
...issues[sub_group_id],
|
...issues[sub_group_id],
|
||||||
[group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)),
|
[group_id]: issues[sub_group_id][group_id].map((i: IIssue) =>
|
||||||
|
i?.id === issue?.id ? { ...i, ...issue } : i
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// if issue is not present in the list then append it
|
||||||
|
else
|
||||||
|
issues = {
|
||||||
|
...issues,
|
||||||
|
[sub_group_id]: {
|
||||||
|
...issues[sub_group_id],
|
||||||
|
[group_id]: [...issues[sub_group_id][group_id], issue],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (issueType === "ungrouped") {
|
if (issueType === "ungrouped") {
|
||||||
issues = issues as IIssueUnGroupedStructure;
|
issues = (issues || []) as IIssueUnGroupedStructure;
|
||||||
issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i));
|
const currentIssue = issues?.find((i: IIssue) => i?.id === issue?.id);
|
||||||
|
if (currentIssue) issues = issues?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i));
|
||||||
|
else issues = [...issues, issue];
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || "";
|
||||||
@ -241,11 +276,18 @@ export class IssueDraftStore implements IIssueDraftStore {
|
|||||||
this.updateIssueStructure(group_id, sub_group_id, issueForm as IIssue);
|
this.updateIssueStructure(group_id, sub_group_id, issueForm as IIssue);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.draftIssueService.updateDraftIssue(workspaceSlug, projectId, issueForm?.id!, issueForm);
|
const response = await this.draftIssueService.updateDraftIssue(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueForm?.id!,
|
||||||
|
issueForm
|
||||||
|
);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Updating issue error", error);
|
console.error("Updating issue error", error);
|
||||||
// reverting back to original issues in case of error
|
// reverting back to original issues in case of error
|
||||||
@ -257,10 +299,9 @@ export class IssueDraftStore implements IIssueDraftStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
convertDraftIssueToIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
convertDraftIssueToIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||||
// update draft issue with is_draft being false
|
// TODO: add removing item from draft issue list
|
||||||
this.updateDraftIssue(workspaceSlug, projectId, { id: issueId, is_draft: false });
|
await this.updateDraftIssue(workspaceSlug, projectId, { id: issueId, is_draft: false });
|
||||||
};
|
|
||||||
|
|
||||||
deleteDraftIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
deleteDraftIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
const originalIssues = { ...this.draftIssues };
|
const originalIssues = { ...this.draftIssues };
|
||||||
|
448
web/store/draft-issues/issue_kanban_view.store.ts
Normal file
448
web/store/draft-issues/issue_kanban_view.store.ts
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "../root";
|
||||||
|
import { IIssueType } from "./issue.store";
|
||||||
|
|
||||||
|
export interface IDraftIssueKanBanViewStore {
|
||||||
|
kanBanToggle: {
|
||||||
|
groupByHeaderMinMax: string[];
|
||||||
|
subgroupByIssuesVisibility: string[];
|
||||||
|
};
|
||||||
|
// computed
|
||||||
|
canUserDragDrop: boolean;
|
||||||
|
canUserDragDropVertically: boolean;
|
||||||
|
canUserDragDropHorizontally: boolean;
|
||||||
|
// actions
|
||||||
|
handleKanBanToggle: (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => void;
|
||||||
|
handleSwimlaneDragDrop: (source: any, destination: any) => void;
|
||||||
|
handleDragDrop: (source: any, destination: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: change implementation for draft issues
|
||||||
|
export class DraftIssueKanBanViewStore implements IDraftIssueKanBanViewStore {
|
||||||
|
kanBanToggle: {
|
||||||
|
groupByHeaderMinMax: string[];
|
||||||
|
subgroupByIssuesVisibility: string[];
|
||||||
|
} = { groupByHeaderMinMax: [], subgroupByIssuesVisibility: [] };
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
makeObservable(this, {
|
||||||
|
kanBanToggle: observable,
|
||||||
|
// computed
|
||||||
|
canUserDragDrop: computed,
|
||||||
|
canUserDragDropVertically: computed,
|
||||||
|
canUserDragDropHorizontally: computed,
|
||||||
|
|
||||||
|
// actions
|
||||||
|
handleKanBanToggle: action,
|
||||||
|
handleSwimlaneDragDrop: action,
|
||||||
|
handleDragDrop: action,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canUserDragDrop() {
|
||||||
|
if (
|
||||||
|
this.rootStore?.issueFilter?.userDisplayFilters?.order_by &&
|
||||||
|
this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" &&
|
||||||
|
this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
|
||||||
|
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by)
|
||||||
|
) {
|
||||||
|
if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true;
|
||||||
|
if (
|
||||||
|
this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by &&
|
||||||
|
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by)
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canUserDragDropVertically() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canUserDragDropHorizontally() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||||
|
this.kanBanToggle = {
|
||||||
|
...this.kanBanToggle,
|
||||||
|
[toggle]: this.kanBanToggle[toggle].includes(value)
|
||||||
|
? this.kanBanToggle[toggle].filter((v) => v !== value)
|
||||||
|
: [...this.kanBanToggle[toggle], value],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSwimlaneDragDrop = async (source: any, destination: any) => {
|
||||||
|
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||||
|
const projectId = this.rootStore?.project?.projectId;
|
||||||
|
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||||
|
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||||
|
const currentIssues: any = this.rootStore.issue.getIssues;
|
||||||
|
|
||||||
|
const sortOrderDefaultValue = 65535;
|
||||||
|
|
||||||
|
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) {
|
||||||
|
// update issue payload
|
||||||
|
let updateIssue: any = {
|
||||||
|
workspaceSlug: workspaceSlug,
|
||||||
|
projectId: projectId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// source, destination group and sub group id
|
||||||
|
let droppableSourceColumnId = source.droppableId;
|
||||||
|
droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null;
|
||||||
|
let droppableDestinationColumnId = destination.droppableId;
|
||||||
|
droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null;
|
||||||
|
if (!droppableSourceColumnId || !droppableDestinationColumnId) return null;
|
||||||
|
|
||||||
|
const source_group_id: string = droppableSourceColumnId[0];
|
||||||
|
const source_sub_group_id: string = droppableSourceColumnId[1] === "null" ? null : droppableSourceColumnId[1];
|
||||||
|
|
||||||
|
const destination_group_id: string = droppableDestinationColumnId[0];
|
||||||
|
const destination_sub_group_id: string =
|
||||||
|
droppableDestinationColumnId[1] === "null" ? null : droppableDestinationColumnId[1];
|
||||||
|
|
||||||
|
if (source_sub_group_id === destination_sub_group_id) {
|
||||||
|
if (source_group_id === destination_group_id) {
|
||||||
|
const _issues = currentIssues[source_sub_group_id][source_group_id];
|
||||||
|
|
||||||
|
// update the sort order
|
||||||
|
if (destination.index === 0) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else if (destination.index === _issues.length - 1) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = _issues.splice(source.index, 1);
|
||||||
|
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||||
|
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||||
|
currentIssues[source_sub_group_id][source_group_id] = _issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_group_id != destination_group_id) {
|
||||||
|
const _sourceIssues = currentIssues[source_sub_group_id][source_group_id];
|
||||||
|
let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || [];
|
||||||
|
|
||||||
|
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||||
|
if (destination.index === 0) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else if (destination.index === _destinationIssues.length) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order:
|
||||||
|
(_destinationIssues[destination.index - 1].sort_order +
|
||||||
|
_destinationIssues[destination.index].sort_order) /
|
||||||
|
2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let issueStatePriority = {};
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") {
|
||||||
|
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||||
|
issueStatePriority = { ...issueStatePriority, state: destination_group_id };
|
||||||
|
}
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") {
|
||||||
|
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||||
|
issueStatePriority = { ...issueStatePriority, priority: destination_group_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||||
|
if (_destinationIssues && _destinationIssues.length > 0)
|
||||||
|
_destinationIssues.splice(destination.index, 0, {
|
||||||
|
...removed,
|
||||||
|
sort_order: updateIssue.sort_order,
|
||||||
|
...issueStatePriority,
|
||||||
|
});
|
||||||
|
else
|
||||||
|
_destinationIssues = [
|
||||||
|
..._destinationIssues,
|
||||||
|
{ ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority },
|
||||||
|
];
|
||||||
|
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||||
|
|
||||||
|
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||||
|
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_sub_group_id != destination_sub_group_id) {
|
||||||
|
const _sourceIssues = currentIssues[source_sub_group_id][source_group_id];
|
||||||
|
let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || [];
|
||||||
|
|
||||||
|
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||||
|
if (destination.index === 0) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else if (destination.index === _destinationIssues.length) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order:
|
||||||
|
(_destinationIssues[destination.index - 1].sort_order +
|
||||||
|
_destinationIssues[destination.index].sort_order) /
|
||||||
|
2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let issueStatePriority = {};
|
||||||
|
if (source_group_id === destination_group_id) {
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "state") {
|
||||||
|
updateIssue = { ...updateIssue, state: destination_sub_group_id };
|
||||||
|
issueStatePriority = { ...issueStatePriority, state: destination_sub_group_id };
|
||||||
|
}
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "priority") {
|
||||||
|
updateIssue = { ...updateIssue, priority: destination_sub_group_id };
|
||||||
|
issueStatePriority = { ...issueStatePriority, priority: destination_sub_group_id };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "state") {
|
||||||
|
updateIssue = { ...updateIssue, state: destination_sub_group_id, priority: destination_group_id };
|
||||||
|
issueStatePriority = {
|
||||||
|
...issueStatePriority,
|
||||||
|
state: destination_sub_group_id,
|
||||||
|
priority: destination_group_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "priority") {
|
||||||
|
updateIssue = { ...updateIssue, state: destination_group_id, priority: destination_sub_group_id };
|
||||||
|
issueStatePriority = {
|
||||||
|
...issueStatePriority,
|
||||||
|
state: destination_group_id,
|
||||||
|
priority: destination_sub_group_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||||
|
if (_destinationIssues && _destinationIssues.length > 0)
|
||||||
|
_destinationIssues.splice(destination.index, 0, {
|
||||||
|
...removed,
|
||||||
|
sort_order: updateIssue.sort_order,
|
||||||
|
...issueStatePriority,
|
||||||
|
});
|
||||||
|
else
|
||||||
|
_destinationIssues = [
|
||||||
|
..._destinationIssues,
|
||||||
|
{ ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority },
|
||||||
|
];
|
||||||
|
|
||||||
|
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||||
|
currentIssues[source_sub_group_id][source_group_id] = _sourceIssues;
|
||||||
|
currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reorderedIssues = {
|
||||||
|
...this.rootStore?.issue.issues,
|
||||||
|
[projectId]: {
|
||||||
|
...this.rootStore?.issue.issues?.[projectId],
|
||||||
|
[issueType]: {
|
||||||
|
...this.rootStore?.issue.issues?.[projectId]?.[issueType],
|
||||||
|
[issueType]: currentIssues,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.rootStore.issue.issues = { ...reorderedIssues };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore.issueDetail?.updateIssue(
|
||||||
|
updateIssue.workspaceSlug,
|
||||||
|
updateIssue.projectId,
|
||||||
|
updateIssue.issueId,
|
||||||
|
updateIssue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDragDrop = async (source: any, destination: any) => {
|
||||||
|
const workspaceSlug = this.rootStore?.workspace?.workspaceSlug;
|
||||||
|
const projectId = this.rootStore?.project?.projectId;
|
||||||
|
const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType;
|
||||||
|
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
|
||||||
|
const currentIssues: any = this.rootStore.issue.getIssues;
|
||||||
|
|
||||||
|
const sortOrderDefaultValue = 65535;
|
||||||
|
|
||||||
|
if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) {
|
||||||
|
// update issue payload
|
||||||
|
let updateIssue: any = {
|
||||||
|
workspaceSlug: workspaceSlug,
|
||||||
|
projectId: projectId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// source, destination group and sub group id
|
||||||
|
let droppableSourceColumnId = source.droppableId;
|
||||||
|
droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null;
|
||||||
|
let droppableDestinationColumnId = destination.droppableId;
|
||||||
|
droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null;
|
||||||
|
if (!droppableSourceColumnId || !droppableDestinationColumnId) return null;
|
||||||
|
|
||||||
|
const source_group_id: string = droppableSourceColumnId[0];
|
||||||
|
const destination_group_id: string = droppableDestinationColumnId[0];
|
||||||
|
|
||||||
|
if (this.canUserDragDrop) {
|
||||||
|
// vertical
|
||||||
|
if (source_group_id === destination_group_id) {
|
||||||
|
const _issues = currentIssues[source_group_id];
|
||||||
|
|
||||||
|
// update the sort order
|
||||||
|
if (destination.index === 0) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else if (destination.index === _issues.length - 1) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = _issues.splice(source.index, 1);
|
||||||
|
_issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order });
|
||||||
|
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||||
|
currentIssues[source_group_id] = _issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// horizontal
|
||||||
|
if (source_group_id != destination_group_id) {
|
||||||
|
const _sourceIssues = currentIssues[source_group_id];
|
||||||
|
let _destinationIssues = currentIssues[destination_group_id] || [];
|
||||||
|
|
||||||
|
if (_destinationIssues && _destinationIssues.length > 0) {
|
||||||
|
if (destination.index === 0) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else if (destination.index === _destinationIssues.length) {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order:
|
||||||
|
(_destinationIssues[destination.index - 1].sort_order +
|
||||||
|
_destinationIssues[destination.index].sort_order) /
|
||||||
|
2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateIssue = {
|
||||||
|
...updateIssue,
|
||||||
|
sort_order: sortOrderDefaultValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let issueStatePriority = {};
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") {
|
||||||
|
updateIssue = { ...updateIssue, state: destination_group_id };
|
||||||
|
issueStatePriority = { ...issueStatePriority, state: destination_group_id };
|
||||||
|
}
|
||||||
|
if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") {
|
||||||
|
updateIssue = { ...updateIssue, priority: destination_group_id };
|
||||||
|
issueStatePriority = { ...issueStatePriority, priority: destination_group_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = _sourceIssues.splice(source.index, 1);
|
||||||
|
if (_destinationIssues && _destinationIssues.length > 0)
|
||||||
|
_destinationIssues.splice(destination.index, 0, {
|
||||||
|
...removed,
|
||||||
|
sort_order: updateIssue.sort_order,
|
||||||
|
...issueStatePriority,
|
||||||
|
});
|
||||||
|
else
|
||||||
|
_destinationIssues = [
|
||||||
|
..._destinationIssues,
|
||||||
|
{ ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority },
|
||||||
|
];
|
||||||
|
updateIssue = { ...updateIssue, issueId: removed?.id };
|
||||||
|
|
||||||
|
currentIssues[source_group_id] = _sourceIssues;
|
||||||
|
currentIssues[destination_group_id] = _destinationIssues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// user can drag the issues only vertically
|
||||||
|
if (this.canUserDragDropVertically && destination_group_id === destination_group_id) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// user can drag the issues only horizontally
|
||||||
|
if (this.canUserDragDropHorizontally && destination_group_id != destination_group_id) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const reorderedIssues = {
|
||||||
|
...this.rootStore?.issue.issues,
|
||||||
|
[projectId]: {
|
||||||
|
...this.rootStore?.issue.issues?.[projectId],
|
||||||
|
[issueType]: {
|
||||||
|
...this.rootStore?.issue.issues?.[projectId]?.[issueType],
|
||||||
|
[issueType]: currentIssues,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.rootStore.issue.issues = { ...reorderedIssues };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore.issueDetail?.updateIssue(
|
||||||
|
updateIssue.workspaceSlug,
|
||||||
|
updateIssue.projectId,
|
||||||
|
updateIssue.issueId,
|
||||||
|
updateIssue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
export * from "./issue_detail.store";
|
export * from "./issue_detail.store";
|
||||||
export * from "./issue_draft.store";
|
|
||||||
export * from "./issue_filters.store";
|
export * from "./issue_filters.store";
|
||||||
export * from "./issue_kanban_view.store";
|
export * from "./issue_kanban_view.store";
|
||||||
export * from "./issue_calendar_view.store";
|
export * from "./issue_calendar_view.store";
|
||||||
|
@ -72,7 +72,14 @@ import {
|
|||||||
ArchivedIssueFilterStore,
|
ArchivedIssueFilterStore,
|
||||||
IArchivedIssueFilterStore,
|
IArchivedIssueFilterStore,
|
||||||
} from "store/archived-issues";
|
} from "store/archived-issues";
|
||||||
import { DraftIssueFilterStore, IDraftIssueFilterStore, IssueDraftStore, IIssueDraftStore } from "store/draft-issues";
|
import {
|
||||||
|
DraftIssueFilterStore,
|
||||||
|
IDraftIssueFilterStore,
|
||||||
|
IssueDraftStore,
|
||||||
|
IIssueDraftStore,
|
||||||
|
DraftIssueKanBanViewStore,
|
||||||
|
IDraftIssueKanBanViewStore,
|
||||||
|
} from "store/draft-issues";
|
||||||
import {
|
import {
|
||||||
IInboxFiltersStore,
|
IInboxFiltersStore,
|
||||||
IInboxIssueDetailsStore,
|
IInboxIssueDetailsStore,
|
||||||
@ -134,6 +141,7 @@ export class RootStore {
|
|||||||
|
|
||||||
draftIssues: IIssueDraftStore;
|
draftIssues: IIssueDraftStore;
|
||||||
draftIssueFilters: IDraftIssueFilterStore;
|
draftIssueFilters: IDraftIssueFilterStore;
|
||||||
|
draftIssueKanBanView: IDraftIssueKanBanViewStore;
|
||||||
|
|
||||||
inbox: IInboxStore;
|
inbox: IInboxStore;
|
||||||
inboxIssues: IInboxIssuesStore;
|
inboxIssues: IInboxIssuesStore;
|
||||||
@ -188,6 +196,7 @@ export class RootStore {
|
|||||||
|
|
||||||
this.draftIssues = new IssueDraftStore(this);
|
this.draftIssues = new IssueDraftStore(this);
|
||||||
this.draftIssueFilters = new DraftIssueFilterStore(this);
|
this.draftIssueFilters = new DraftIssueFilterStore(this);
|
||||||
|
this.draftIssueKanBanView = new DraftIssueKanBanViewStore(this);
|
||||||
|
|
||||||
this.inbox = new InboxStore(this);
|
this.inbox = new InboxStore(this);
|
||||||
this.inboxIssues = new InboxIssuesStore(this);
|
this.inboxIssues = new InboxIssuesStore(this);
|
||||||
|
Loading…
Reference in New Issue
Block a user