forked from github/plane
refactor: modules list page (#2521)
* refactor: modules list page * chore: update handle favorites logic for modules * fix: build errors
This commit is contained in:
parent
07d548ea43
commit
d72d3da6de
@ -10,7 +10,7 @@ export * from "./workspace-dashboard";
|
|||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./profile-preferences";
|
export * from "./profile-preferences";
|
||||||
export * from "./cycles";
|
export * from "./cycles";
|
||||||
export * from "./modules";
|
export * from "./modules-list";
|
||||||
export * from "./project-settings";
|
export * from "./project-settings";
|
||||||
export * from "./workspace-settings";
|
export * from "./workspace-settings";
|
||||||
export * from "./pages";
|
export * from "./pages";
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import { Dispatch, FC, SetStateAction } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
// components
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// hooks
|
||||||
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, BreadcrumbItem, Button, Tooltip } from "@plane/ui";
|
import { Breadcrumbs, BreadcrumbItem, Button, Tooltip } from "@plane/ui";
|
||||||
import { Icon } from "components/ui";
|
import { Icon } from "components/ui";
|
||||||
// helper
|
// helper
|
||||||
import { replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
|
import { replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
|
||||||
|
|
||||||
export interface IModulesHeader {
|
|
||||||
name: string | undefined;
|
|
||||||
modulesView: string;
|
|
||||||
setModulesView: Dispatch<SetStateAction<"grid" | "gantt_chart">>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
|
const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
|
||||||
{
|
{
|
||||||
type: "gantt_chart",
|
type: "gantt_chart",
|
||||||
@ -26,11 +23,15 @@ const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ModulesHeader: FC<IModulesHeader> = (props) => {
|
export const ModulesListHeader: React.FC = observer(() => {
|
||||||
const { name, modulesView, setModulesView } = props;
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { project: projectStore } = useMobxStore();
|
||||||
|
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
|
||||||
|
|
||||||
|
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -48,7 +49,7 @@ export const ModulesHeader: FC<IModulesHeader> = (props) => {
|
|||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<BreadcrumbItem title={`${truncateText(name ?? "Project", 32)} Modules`} />
|
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Modules`} />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,4 +84,4 @@ export const ModulesHeader: FC<IModulesHeader> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
@ -119,7 +119,7 @@ export const CycleIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
setDeleteIssueModal(true);
|
setDeleteIssueModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 text-red-500">
|
<div className="flex items-center gap-2">
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
Delete issue
|
Delete issue
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,7 +119,7 @@ export const ModuleIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
setDeleteIssueModal(true);
|
setDeleteIssueModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 text-red-500">
|
<div className="flex items-center gap-2">
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
Delete issue
|
Delete issue
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +106,7 @@ export const ProjectIssueQuickActions: React.FC<Props> = (props) => {
|
|||||||
setDeleteIssueModal(true);
|
setDeleteIssueModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 text-red-500">
|
<div className="flex items-center gap-2">
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
Delete issue
|
Delete issue
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,7 @@ 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 { ModuleService } from "services/module.service";
|
import { IssueDraftService } from "services/issue";
|
||||||
import { IssueService, 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";
|
||||||
@ -40,8 +39,6 @@ export interface IssuesModalProps {
|
|||||||
onSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
onSubmit?: (data: Partial<IIssue>) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleService = new ModuleService();
|
|
||||||
const issueService = new IssueService();
|
|
||||||
const issueDraftService = new IssueDraftService();
|
const issueDraftService = new IssueDraftService();
|
||||||
|
|
||||||
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
||||||
@ -170,35 +167,15 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
}, [activeProject, data, projectId, projects, isOpen]);
|
}, [activeProject, data, projectId, projects, isOpen]);
|
||||||
|
|
||||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject || !user) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
await issueService
|
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, issueId);
|
||||||
.addIssueToCycle(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
activeProject,
|
|
||||||
cycleId,
|
|
||||||
{
|
|
||||||
issues: [issueId],
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => cycleIssueStore.fetchIssues(workspaceSlug.toString(), activeProject, cycleId));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject || !user) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
await moduleService
|
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, issueId);
|
||||||
.addIssuesToModule(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
activeProject,
|
|
||||||
moduleId,
|
|
||||||
{
|
|
||||||
issues: [issueId],
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => moduleIssueStore.fetchIssues(workspaceSlug.toString(), activeProject, moduleId));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createIssue = async (payload: Partial<IIssue>) => {
|
const createIssue = async (payload: Partial<IIssue>) => {
|
||||||
@ -267,10 +244,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateIssue = async (payload: Partial<IIssue>) => {
|
const updateIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!user) return;
|
if (!workspaceSlug || !activeProject || !data) return;
|
||||||
|
|
||||||
await issueService
|
await issueDetailStore
|
||||||
.patchIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload, user)
|
.updateIssue(workspaceSlug.toString(), activeProject, data.id, payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (!createMore) onFormSubmitClose();
|
if (!createMore) onFormSubmitClose();
|
||||||
|
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { mutate } from "swr";
|
|
||||||
|
|
||||||
// headless ui
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -15,42 +8,40 @@ import { Button } from "@plane/ui";
|
|||||||
// icons
|
// icons
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import type { IUser, IModule } from "types";
|
import type { IModule } from "types";
|
||||||
// fetch-keys
|
import { observer } from "mobx-react-lite";
|
||||||
import { MODULE_LIST } from "constants/fetch-keys";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
data: IModule;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
onClose: () => void;
|
||||||
data?: IModule;
|
|
||||||
user: IUser | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// services
|
export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||||
const moduleService = new ModuleService();
|
const { data, isOpen, onClose } = props;
|
||||||
|
|
||||||
export const DeleteModuleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, user }) => {
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { module: moduleStore } = useMobxStore();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsOpen(false);
|
onClose();
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeletion = async () => {
|
const handleDeletion = async () => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId || !data) return;
|
await moduleStore
|
||||||
|
.deleteModule(workspaceSlug.toString(), projectId.toString(), data.id)
|
||||||
mutate<IModule[]>(MODULE_LIST(projectId as string), (prevData) => prevData?.filter((m) => m.id !== data.id), false);
|
|
||||||
|
|
||||||
await moduleService
|
|
||||||
.deleteModule(workspaceSlug as string, projectId as string, data.id, user)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (moduleId) router.push(`/${workspaceSlug}/projects/${data.project}/modules`);
|
if (moduleId) router.push(`/${workspaceSlug}/projects/${data.project}/modules`);
|
||||||
handleClose();
|
handleClose();
|
||||||
@ -61,6 +52,8 @@ export const DeleteModuleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, us
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Module could not be deleted. Please try again.",
|
message: "Module could not be deleted. Please try again.",
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -126,4 +119,4 @@ export const DeleteModuleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, us
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,65 +1,26 @@
|
|||||||
import { FC } from "react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { KeyedMutator } from "swr";
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
// hooks
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, IBlockUpdateData } from "components/gantt-chart";
|
import { GanttChartRoot, IBlockUpdateData } from "components/gantt-chart";
|
||||||
import { ModuleGanttBlock, ModuleGanttSidebarBlock } from "components/modules";
|
import { ModuleGanttBlock, ModuleGanttSidebarBlock } from "components/modules";
|
||||||
// types
|
// types
|
||||||
import { IModule } from "types";
|
import { IModule } from "types";
|
||||||
|
|
||||||
type Props = {
|
export const ModulesListGanttChartView: React.FC = observer(() => {
|
||||||
modules: IModule[];
|
|
||||||
mutateModules: KeyedMutator<IModule[]>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// services
|
|
||||||
const moduleService = new ModuleService();
|
|
||||||
|
|
||||||
export const ModulesListGanttChartView: FC<Props> = ({ modules, mutateModules }) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { user } = useUser();
|
const { project: projectStore, module: moduleStore } = useMobxStore();
|
||||||
const { projectDetails } = useProjectDetails();
|
|
||||||
|
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
|
||||||
|
const modules = moduleStore.projectModules;
|
||||||
|
|
||||||
const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => {
|
const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => {
|
||||||
if (!workspaceSlug || !user) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
mutateModules((prevData: any) => {
|
moduleStore.updateModuleGanttStructure(workspaceSlug.toString(), module.project, module, payload);
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
const newList = prevData.map((p: any) => ({
|
|
||||||
...p,
|
|
||||||
...(p.id === module.id
|
|
||||||
? {
|
|
||||||
start_date: payload.start_date ? payload.start_date : p.start_date,
|
|
||||||
target_date: payload.target_date ? payload.target_date : p.target_date,
|
|
||||||
sort_order: payload.sort_order ? payload.sort_order.newSortOrder : p.sort_order,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (payload.sort_order) {
|
|
||||||
const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0];
|
|
||||||
newList.splice(payload.sort_order.destinationIndex, 0, removedElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newList;
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
const newPayload: any = { ...payload };
|
|
||||||
|
|
||||||
if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;
|
|
||||||
|
|
||||||
moduleService.patchModule(workspaceSlug.toString(), module.project, module.id, newPayload, user);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: IModule[]) =>
|
const blockFormat = (blocks: IModule[]) =>
|
||||||
@ -93,4 +54,4 @@ export const ModulesListGanttChartView: FC<Props> = ({ modules, mutateModules })
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -4,5 +4,6 @@ export * from "./delete-module-modal";
|
|||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./gantt-chart";
|
export * from "./gantt-chart";
|
||||||
export * from "./modal";
|
export * from "./modal";
|
||||||
|
export * from "./modules-list-view";
|
||||||
export * from "./sidebar";
|
export * from "./sidebar";
|
||||||
export * from "./single-module-card";
|
export * from "./module-card-item";
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { mutate } from "swr";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { ModuleForm } from "components/modules";
|
import { ModuleForm } from "components/modules";
|
||||||
// services
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// types
|
// types
|
||||||
import type { IUser, IModule } from "types";
|
import type { IModule } from "types";
|
||||||
// fetch-keys
|
|
||||||
import { MODULE_LIST } from "constants/fetch-keys";
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -30,14 +27,12 @@ const defaultValues: Partial<IModule> = {
|
|||||||
members_list: [],
|
members_list: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const moduleService = new ModuleService();
|
export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
export const CreateUpdateModuleModal: React.FC<Props> = (props) => {
|
|
||||||
const { isOpen, onClose, data, workspaceSlug, projectId } = props;
|
const { isOpen, onClose, data, workspaceSlug, projectId } = props;
|
||||||
|
|
||||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||||
|
|
||||||
const { project: projectStore } = useMobxStore();
|
const { project: projectStore, module: moduleStore } = useMobxStore();
|
||||||
|
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||||
|
|
||||||
@ -53,10 +48,11 @@ export const CreateUpdateModuleModal: React.FC<Props> = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const createModule = async (payload: Partial<IModule>) => {
|
const createModule = async (payload: Partial<IModule>) => {
|
||||||
await moduleService
|
if (!workspaceSlug || !projectId) return;
|
||||||
.createModule(workspaceSlug as string, projectId as string, payload, {} as IUser)
|
|
||||||
|
await moduleStore
|
||||||
|
.createModule(workspaceSlug.toString(), projectId.toString(), payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(MODULE_LIST(projectId as string));
|
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -75,19 +71,11 @@ export const CreateUpdateModuleModal: React.FC<Props> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateModule = async (payload: Partial<IModule>) => {
|
const updateModule = async (payload: Partial<IModule>) => {
|
||||||
await moduleService
|
if (!workspaceSlug || !projectId || !data) return;
|
||||||
.updateModule(workspaceSlug as string, projectId as string, data?.id ?? "", payload, {} as IUser)
|
|
||||||
.then((res) => {
|
|
||||||
mutate<IModule[]>(
|
|
||||||
MODULE_LIST(projectId as string),
|
|
||||||
(prevData) =>
|
|
||||||
prevData?.map((p) => {
|
|
||||||
if (p.id === res.id) return { ...p, ...payload };
|
|
||||||
|
|
||||||
return p;
|
await moduleStore
|
||||||
}),
|
.updateModuleDetails(workspaceSlug.toString(), projectId.toString(), data.id, payload)
|
||||||
false
|
.then(() => {
|
||||||
);
|
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -180,4 +168,4 @@ export const CreateUpdateModuleModal: React.FC<Props> = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,35 +1,32 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { mutate } from "swr";
|
import { observer } from "mobx-react-lite";
|
||||||
// services
|
// mobx store
|
||||||
import { ModuleService } from "services/module.service";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { DeleteModuleModal } from "components/modules";
|
import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules";
|
||||||
// ui
|
// ui
|
||||||
import { AssigneesList } from "components/ui";
|
import { AssigneesList } from "components/ui";
|
||||||
import { CustomMenu, Tooltip } from "@plane/ui";
|
import { CustomMenu, Tooltip } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
import { CalendarDays, LinkIcon, Pencil, Star, Target, Trash2 } from "lucide-react";
|
import { CalendarDays, LinkIcon, Pencil, Star, Target, Trash2 } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
import { copyUrlToClipboard, truncateText } from "helpers/string.helper";
|
||||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { IUser, IModule } from "types";
|
import { IModule } from "types";
|
||||||
// fetch-key
|
|
||||||
import { MODULE_LIST } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
module: IModule;
|
module: IModule;
|
||||||
handleEditModule: () => void;
|
|
||||||
user: IUser | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const moduleService = new ModuleService();
|
export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
|
const { module } = props;
|
||||||
|
|
||||||
export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, user }) => {
|
const [editModuleModal, setEditModuleModal] = useState(false);
|
||||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -37,54 +34,26 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
const { module: moduleStore } = useMobxStore();
|
||||||
|
|
||||||
const completionPercentage = ((module.completed_issues + module.cancelled_issues) / module.total_issues) * 100;
|
const completionPercentage = ((module.completed_issues + module.cancelled_issues) / module.total_issues) * 100;
|
||||||
|
|
||||||
const handleDeleteModule = () => {
|
|
||||||
if (!module) return;
|
|
||||||
|
|
||||||
setModuleDeleteModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !module) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
mutate<IModule[]>(
|
moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => {
|
||||||
MODULE_LIST(projectId as string),
|
setToastAlert({
|
||||||
(prevData) =>
|
type: "error",
|
||||||
(prevData ?? []).map((m) => ({
|
title: "Error!",
|
||||||
...m,
|
message: "Couldn't add the module to favorites. Please try again.",
|
||||||
is_favorite: m.id === module.id ? true : m.is_favorite,
|
|
||||||
})),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
moduleService
|
|
||||||
.addModuleToFavorites(workspaceSlug as string, projectId as string, {
|
|
||||||
module: module.id,
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: "Couldn't add the module to favorites. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = () => {
|
const handleRemoveFromFavorites = () => {
|
||||||
if (!workspaceSlug || !projectId || !module) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
mutate<IModule[]>(
|
moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => {
|
||||||
MODULE_LIST(projectId as string),
|
|
||||||
(prevData) =>
|
|
||||||
(prevData ?? []).map((m) => ({
|
|
||||||
...m,
|
|
||||||
is_favorite: m.id === module.id ? false : m.is_favorite,
|
|
||||||
})),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
moduleService.removeModuleFromFavorites(workspaceSlug as string, projectId as string, module.id).catch(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -94,9 +63,7 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => {
|
||||||
|
|
||||||
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
@ -111,7 +78,16 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DeleteModuleModal isOpen={moduleDeleteModal} setIsOpen={setModuleDeleteModal} data={module} user={user} />
|
{workspaceSlug && projectId && (
|
||||||
|
<CreateUpdateModuleModal
|
||||||
|
isOpen={editModuleModal}
|
||||||
|
onClose={() => setEditModuleModal(false)}
|
||||||
|
data={module}
|
||||||
|
projectId={projectId.toString()}
|
||||||
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<DeleteModuleModal data={module} isOpen={moduleDeleteModal} onClose={() => setModuleDeleteModal(false)} />
|
||||||
<div className="flex flex-col divide-y divide-custom-border-200 overflow-hidden rounded-[10px] border border-custom-border-200 bg-custom-background-100 text-xs">
|
<div className="flex flex-col divide-y divide-custom-border-200 overflow-hidden rounded-[10px] border border-custom-border-200 bg-custom-background-100 text-xs">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex w-full flex-col gap-5">
|
<div className="flex w-full flex-col gap-5">
|
||||||
@ -140,25 +116,25 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CustomMenu width="auto" verticalEllipsis>
|
<CustomMenu width="auto" verticalEllipsis placement="bottom-end">
|
||||||
<CustomMenu.MenuItem onClick={handleEditModule}>
|
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||||
<span className="flex items-center justify-start gap-2">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<Pencil className="h-4 w-4" />
|
<LinkIcon className="h-3 w-3" strokeWidth={2} />
|
||||||
|
<span>Copy link</span>
|
||||||
|
</span>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem onClick={() => setEditModuleModal(true)}>
|
||||||
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<Pencil className="h-3 w-3" strokeWidth={2} />
|
||||||
<span>Edit module</span>
|
<span>Edit module</span>
|
||||||
</span>
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={handleDeleteModule}>
|
<CustomMenu.MenuItem onClick={() => setModuleDeleteModal(true)}>
|
||||||
<span className="flex items-center justify-start gap-2">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-3 w-3" strokeWidth={2} />
|
||||||
<span>Delete module</span>
|
<span>Delete module</span>
|
||||||
</span>
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
|
||||||
<span className="flex items-center justify-start gap-2">
|
|
||||||
<LinkIcon className="h-4 w-4" />
|
|
||||||
<span>Copy module link</span>
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -204,4 +180,4 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
68
web/components/modules/modules-list-view.tsx
Normal file
68
web/components/modules/modules-list-view.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// hooks
|
||||||
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
|
// components
|
||||||
|
import { ModuleCardItem, ModulesListGanttChartView } from "components/modules";
|
||||||
|
import { EmptyState } from "components/common";
|
||||||
|
// ui
|
||||||
|
import { Loader } from "@plane/ui";
|
||||||
|
// assets
|
||||||
|
import emptyModule from "public/empty-state/module.svg";
|
||||||
|
|
||||||
|
export const ModulesListView: React.FC = observer(() => {
|
||||||
|
const { module: moduleStore } = useMobxStore();
|
||||||
|
|
||||||
|
const { storedValue: modulesView } = useLocalStorage("modules_view", "grid");
|
||||||
|
|
||||||
|
const modulesList = moduleStore.projectModules;
|
||||||
|
|
||||||
|
if (!modulesList)
|
||||||
|
return (
|
||||||
|
<Loader className="grid grid-cols-3 gap-4 p-8">
|
||||||
|
<Loader.Item height="100px" />
|
||||||
|
<Loader.Item height="100px" />
|
||||||
|
<Loader.Item height="100px" />
|
||||||
|
<Loader.Item height="100px" />
|
||||||
|
<Loader.Item height="100px" />
|
||||||
|
<Loader.Item height="100px" />
|
||||||
|
</Loader>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{modulesList.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{modulesView === "grid" && (
|
||||||
|
<div className="h-full overflow-y-auto p-8">
|
||||||
|
<div className="grid grid-cols-1 gap-9 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{modulesList.map((module) => (
|
||||||
|
<ModuleCardItem key={module.id} module={module} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{modulesView === "gantt_chart" && <ModulesListGanttChartView />}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<EmptyState
|
||||||
|
title="Manage your project with modules"
|
||||||
|
description="Modules are smaller, focused projects that help you group and organize issues."
|
||||||
|
image={emptyModule}
|
||||||
|
primaryButton={{
|
||||||
|
icon: <Plus className="h-4 w-4" />,
|
||||||
|
text: "New Module",
|
||||||
|
onClick: () => {
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
key: "m",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -228,7 +228,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
createIssueLink={handleCreateLink}
|
createIssueLink={handleCreateLink}
|
||||||
updateIssueLink={handleUpdateLink}
|
updateIssueLink={handleUpdateLink}
|
||||||
/>
|
/>
|
||||||
<DeleteModuleModal isOpen={moduleDeleteModal} setIsOpen={setModuleDeleteModal} data={moduleDetails} user={user} />
|
<DeleteModuleModal isOpen={moduleDeleteModal} onClose={() => setModuleDeleteModal(false)} data={moduleDetails} />
|
||||||
<div
|
<div
|
||||||
className={`fixed top-[66px] ${
|
className={`fixed top-[66px] ${
|
||||||
isOpen ? "right-0" : "-right-[24rem]"
|
isOpen ? "right-0" : "-right-[24rem]"
|
||||||
|
@ -205,6 +205,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
className="hidden group-hover:block flex-shrink-0"
|
className="hidden group-hover:block flex-shrink-0"
|
||||||
buttonClassName="!text-custom-sidebar-text-400 hover:text-custom-sidebar-text-400"
|
buttonClassName="!text-custom-sidebar-text-400 hover:text-custom-sidebar-text-400"
|
||||||
ellipsis
|
ellipsis
|
||||||
|
placement="bottom-start"
|
||||||
>
|
>
|
||||||
{!shortContextMenu && isAdmin && (
|
{!shortContextMenu && isAdmin && (
|
||||||
<CustomMenu.MenuItem onClick={handleDeleteProjectClick}>
|
<CustomMenu.MenuItem onClick={handleDeleteProjectClick}>
|
||||||
|
@ -16,8 +16,10 @@ import { ModuleIssuesHeader } from "components/headers";
|
|||||||
import { EmptyState } from "components/common";
|
import { EmptyState } from "components/common";
|
||||||
// assets
|
// assets
|
||||||
import emptyModule from "public/empty-state/module.svg";
|
import emptyModule from "public/empty-state/module.svg";
|
||||||
|
// types
|
||||||
|
import { NextPage } from "next";
|
||||||
|
|
||||||
const SingleModule: React.FC = () => {
|
const ModuleIssuesPage: NextPage = () => {
|
||||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -91,4 +93,4 @@ const SingleModule: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SingleModule;
|
export default ModuleIssuesPage;
|
||||||
|
@ -1,133 +1,15 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
// layouts
|
// layouts
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
// hooks
|
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
|
||||||
// services
|
|
||||||
import { ProjectService } from "services/project";
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateModuleModal, ModulesListGanttChartView, SingleModuleCard } from "components/modules";
|
import { ModulesListView } from "components/modules";
|
||||||
import { ModulesHeader } from "components/headers";
|
import { ModulesListHeader } from "components/headers";
|
||||||
// ui
|
|
||||||
import { Loader } from "@plane/ui";
|
|
||||||
import { EmptyState } from "components/common";
|
|
||||||
// icons
|
|
||||||
import { Plus } from "lucide-react";
|
|
||||||
// images
|
|
||||||
import emptyModule from "public/empty-state/module.svg";
|
|
||||||
// types
|
|
||||||
import { IModule, SelectModuleType } from "types/modules";
|
|
||||||
// fetch-keys
|
|
||||||
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
// services
|
const ProjectModules: NextPage = () => (
|
||||||
const projectService = new ProjectService();
|
<AppLayout header={<ModulesListHeader />} withProjectWrapper>
|
||||||
const moduleService = new ModuleService();
|
<ModulesListView />
|
||||||
|
</AppLayout>
|
||||||
const ProjectModules: NextPage = () => {
|
);
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
// states
|
|
||||||
const [modulesView, setModulesView] = useState<"grid" | "gantt_chart">("grid");
|
|
||||||
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
|
||||||
const [createUpdateModule, setCreateUpdateModule] = useState(false);
|
|
||||||
|
|
||||||
const { user } = useUserAuth();
|
|
||||||
|
|
||||||
const { data: activeProject } = useSWR(
|
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
|
||||||
workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: modules, mutate: mutateModules } = useSWR(
|
|
||||||
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
|
|
||||||
workspaceSlug && projectId ? () => moduleService.getModules(workspaceSlug as string, projectId as string) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleEditModule = (module: IModule) => {
|
|
||||||
setSelectedModule({ ...module, actionType: "edit" });
|
|
||||||
setCreateUpdateModule(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (createUpdateModule) return;
|
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setSelectedModule(undefined);
|
|
||||||
clearTimeout(timer);
|
|
||||||
}, 500);
|
|
||||||
}, [createUpdateModule]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppLayout
|
|
||||||
header={<ModulesHeader name={activeProject?.name} modulesView={modulesView} setModulesView={setModulesView} />}
|
|
||||||
>
|
|
||||||
{workspaceSlug && projectId && (
|
|
||||||
<CreateUpdateModuleModal
|
|
||||||
isOpen={createUpdateModule}
|
|
||||||
onClose={() => {
|
|
||||||
setCreateUpdateModule(false);
|
|
||||||
}}
|
|
||||||
data={selectedModule}
|
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
|
||||||
projectId={projectId.toString()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{modules ? (
|
|
||||||
modules.length > 0 ? (
|
|
||||||
<>
|
|
||||||
{modulesView === "grid" && (
|
|
||||||
<div className="h-full overflow-y-auto p-8">
|
|
||||||
<div className="grid grid-cols-1 gap-9 lg:grid-cols-2 xl:grid-cols-3">
|
|
||||||
{modules.map((module) => (
|
|
||||||
<SingleModuleCard
|
|
||||||
key={module.id}
|
|
||||||
module={module}
|
|
||||||
handleEditModule={() => handleEditModule(module)}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{modulesView === "gantt_chart" && (
|
|
||||||
<ModulesListGanttChartView modules={modules} mutateModules={mutateModules} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<EmptyState
|
|
||||||
title="Manage your project with modules"
|
|
||||||
description="Modules are smaller, focused projects that help you group and organize issues."
|
|
||||||
image={emptyModule}
|
|
||||||
primaryButton={{
|
|
||||||
icon: <Plus className="h-4 w-4" />,
|
|
||||||
text: "New Module",
|
|
||||||
onClick: () => {
|
|
||||||
const e = new KeyboardEvent("keydown", {
|
|
||||||
key: "m",
|
|
||||||
});
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Loader className="grid grid-cols-3 gap-4 p-8">
|
|
||||||
<Loader.Item height="100px" />
|
|
||||||
<Loader.Item height="100px" />
|
|
||||||
<Loader.Item height="100px" />
|
|
||||||
<Loader.Item height="100px" />
|
|
||||||
<Loader.Item height="100px" />
|
|
||||||
<Loader.Item height="100px" />
|
|
||||||
</Loader>
|
|
||||||
)}
|
|
||||||
</AppLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectModules;
|
export default ProjectModules;
|
||||||
|
@ -61,11 +61,11 @@ export class ModuleService extends APIService {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
moduleId: string,
|
moduleId: string,
|
||||||
data: Partial<IModule>,
|
data: Partial<IModule>,
|
||||||
user: any
|
user: IUser | undefined
|
||||||
): Promise<any> {
|
): Promise<IModule> {
|
||||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data)
|
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
trackEventService.trackModuleEvent(response?.data, "MODULE_UPDATE", user);
|
if (user) trackEventService.trackModuleEvent(response?.data, "MODULE_UPDATE", user);
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -38,7 +38,7 @@ export interface IIssueDetailStore {
|
|||||||
// creating issue
|
// creating issue
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<IIssue>) => Promise<IIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<IIssue>) => Promise<IIssue>;
|
||||||
// updating issue
|
// updating issue
|
||||||
updateIssue: (workspaceId: string, projectId: string, issueId: string, data: Partial<IIssue>) => void;
|
updateIssue: (workspaceId: string, projectId: string, issueId: string, data: Partial<IIssue>) => Promise<IIssue>;
|
||||||
// deleting issue
|
// deleting issue
|
||||||
deleteIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
deleteIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
|
|
||||||
@ -260,6 +260,8 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssueDetails(workspaceSlug, projectId, issueId);
|
this.fetchIssueDetails(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
|
@ -152,6 +152,8 @@ export class ModuleFilterStore implements IModuleFilterStore {
|
|||||||
this.moduleFilters = newFilters;
|
this.moduleFilters = newFilters;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const user = this.rootStore.user.currentUser ?? undefined;
|
||||||
|
|
||||||
this.moduleService.patchModule(
|
this.moduleService.patchModule(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
@ -161,7 +163,7 @@ export class ModuleFilterStore implements IModuleFilterStore {
|
|||||||
filters: newFilters,
|
filters: newFilters,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
this.rootStore.user.currentUser
|
user
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchModuleFilters(workspaceSlug, projectId, moduleId);
|
this.fetchModuleFilters(workspaceSlug, projectId, moduleId);
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
IIssueGroupedStructure,
|
IIssueGroupedStructure,
|
||||||
IIssueUnGroupedStructure,
|
IIssueUnGroupedStructure,
|
||||||
} from "../issue/issue.store";
|
} from "../issue/issue.store";
|
||||||
|
import { IBlockUpdateData } from "components/gantt-chart";
|
||||||
|
|
||||||
export interface IModuleStore {
|
export interface IModuleStore {
|
||||||
// states
|
// states
|
||||||
@ -41,10 +42,21 @@ export interface IModuleStore {
|
|||||||
fetchModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<IModule>;
|
fetchModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<IModule>;
|
||||||
|
|
||||||
createModule: (workspaceSlug: string, projectId: string, data: Partial<IModule>) => Promise<IModule>;
|
createModule: (workspaceSlug: string, projectId: string, data: Partial<IModule>) => Promise<IModule>;
|
||||||
updateModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string, data: Partial<IModule>) => void;
|
updateModuleDetails: (
|
||||||
deleteModule: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
workspaceSlug: string,
|
||||||
addModuleToFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
projectId: string,
|
||||||
removeModuleFromFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
moduleId: string,
|
||||||
|
data: Partial<IModule>
|
||||||
|
) => Promise<IModule>;
|
||||||
|
deleteModule: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
|
||||||
|
addModuleToFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
|
||||||
|
removeModuleFromFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
|
||||||
|
updateModuleGanttStructure: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
module: IModule,
|
||||||
|
payload: IBlockUpdateData
|
||||||
|
) => void;
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
projectModules: IModule[] | null;
|
projectModules: IModule[] | null;
|
||||||
@ -109,6 +121,7 @@ export class ModuleStore implements IModuleStore {
|
|||||||
deleteModule: action,
|
deleteModule: action,
|
||||||
addModuleToFavorites: action,
|
addModuleToFavorites: action,
|
||||||
removeModuleFromFavorites: action,
|
removeModuleFromFavorites: action,
|
||||||
|
updateModuleGanttStructure: action,
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
projectModules: computed,
|
projectModules: computed,
|
||||||
@ -242,7 +255,11 @@ export class ModuleStore implements IModuleStore {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data, this.rootStore.user.currentUser);
|
const user = this.rootStore.user.currentUser ?? undefined;
|
||||||
|
|
||||||
|
const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data, user);
|
||||||
|
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update module in module store", error);
|
console.error("Failed to update module in module store", error);
|
||||||
|
|
||||||
@ -252,6 +269,8 @@ export class ModuleStore implements IModuleStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -334,4 +353,46 @@ export class ModuleStore implements IModuleStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateModuleGanttStructure = (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
module: IModule,
|
||||||
|
payload: IBlockUpdateData
|
||||||
|
) => {
|
||||||
|
const modulesList = this.modules[projectId];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newModules = modulesList?.map((p: any) => ({
|
||||||
|
...p,
|
||||||
|
...(p.id === module.id
|
||||||
|
? {
|
||||||
|
start_date: payload.start_date ? payload.start_date : p.start_date,
|
||||||
|
target_date: payload.target_date ? payload.target_date : p.target_date,
|
||||||
|
sort_order: payload.sort_order ? payload.sort_order.newSortOrder : p.sort_order,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (payload.sort_order) {
|
||||||
|
const removedElement = newModules.splice(payload.sort_order.sourceIndex, 1)[0];
|
||||||
|
newModules.splice(payload.sort_order.destinationIndex, 0, removedElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: newModules,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const newPayload: any = { ...payload };
|
||||||
|
|
||||||
|
if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;
|
||||||
|
|
||||||
|
this.updateModuleDetails(workspaceSlug, module.project, module.id, newPayload);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update module gantt structure in module store", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user