forked from github/plane
refactor: divided modules into multiple components
This commit is contained in:
parent
547a265169
commit
b49b1bf8fa
@ -5,4 +5,5 @@ export * from "./form";
|
||||
export * from "./gantt-chart";
|
||||
export * from "./modal";
|
||||
export * from "./sidebar";
|
||||
export * from "./single-module-card";
|
||||
export * from "./module-detail-card";
|
||||
export * from "./module-list";
|
||||
|
@ -12,16 +12,10 @@ import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { DeleteModuleModal } from "components/modules";
|
||||
// ui
|
||||
import { AssigneesList, Avatar, CustomMenu, Tooltip } from "components/ui";
|
||||
import { AssigneesList, CustomMenu, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
CalendarDaysIcon,
|
||||
LinkIcon,
|
||||
PencilIcon,
|
||||
StarIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { CalendarMonthIcon, TargetIcon } from "components/icons";
|
||||
import { CalendarDaysIcon, LinkIcon, PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { TargetIcon } from "components/icons";
|
||||
|
||||
// helpers
|
||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||
@ -37,7 +31,7 @@ type Props = {
|
||||
user: ICurrentUserResponse | undefined;
|
||||
};
|
||||
|
||||
export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, user }) => {
|
||||
export const ModuleDetailCard: React.FC<Props> = ({ module, handleEditModule, user }) => {
|
||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@ -45,8 +39,7 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
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;
|
||||
@ -93,9 +86,7 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
||||
false
|
||||
);
|
||||
|
||||
modulesService
|
||||
.removeModuleFromFavorites(workspaceSlug as string, projectId as string, module.id)
|
||||
.catch(() => {
|
||||
modulesService.removeModuleFromFavorites(workspaceSlug as string, projectId as string, module.id).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
@ -105,12 +96,9 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
||||
};
|
||||
|
||||
const handleCopyText = () => {
|
||||
const originURL =
|
||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||
|
||||
copyTextToClipboard(
|
||||
`${originURL}/${workspaceSlug}/projects/${projectId}/modules/${module.id}`
|
||||
).then(() => {
|
||||
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Link Copied!",
|
||||
@ -125,12 +113,7 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule, us
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteModuleModal
|
||||
isOpen={moduleDeleteModal}
|
||||
setIsOpen={setModuleDeleteModal}
|
||||
data={module}
|
||||
user={user}
|
||||
/>
|
||||
<DeleteModuleModal isOpen={moduleDeleteModal} setIsOpen={setModuleDeleteModal} data={module} user={user} />
|
||||
<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="flex w-full flex-col gap-5">
|
131
web/components/modules/module-list.tsx
Normal file
131
web/components/modules/module-list.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// hooks
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
// services
|
||||
import modulesService from "services/modules.service";
|
||||
// components
|
||||
import { CreateUpdateModuleModal, ModulesListGanttChartView, ModuleDetailCard } from "components/modules";
|
||||
// ui
|
||||
import { EmptyState, Loader } from "components/ui";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// images
|
||||
import emptyModule from "public/empty-state/module.svg";
|
||||
// types
|
||||
import type { IModule, SelectModuleType } from "types/modules";
|
||||
// fetch-keys
|
||||
import { MODULE_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
modulesView: string;
|
||||
modules?: IModule[] | null;
|
||||
};
|
||||
|
||||
export const ModuleList: React.FC<Props> = (props) => {
|
||||
const { modulesView, modules } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// states
|
||||
const [createUpdateModule, setCreateUpdateModule] = useState(false);
|
||||
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
||||
|
||||
// hooks
|
||||
const { user } = useUserAuth();
|
||||
|
||||
// TODO: remove this
|
||||
const { mutate: mutateModules } = useSWR(
|
||||
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
|
||||
workspaceSlug && projectId ? () => modulesService.getModules(workspaceSlug as string, projectId as string) : null
|
||||
);
|
||||
|
||||
const handleEditModule = (module: IModule) => {
|
||||
setSelectedModule({ ...module, actionType: "edit" });
|
||||
setCreateUpdateModule(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the create/update module
|
||||
* modal and reset the selected module
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (createUpdateModule) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setSelectedModule(undefined);
|
||||
clearTimeout(timer);
|
||||
}, 500);
|
||||
}, [createUpdateModule]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateUpdateModuleModal
|
||||
isOpen={createUpdateModule}
|
||||
setIsOpen={setCreateUpdateModule}
|
||||
data={selectedModule}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
{/* modules are loaded & there number is more than 0 */}
|
||||
{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((moduleDetail) => (
|
||||
<ModuleDetailCard
|
||||
key={moduleDetail.id}
|
||||
module={moduleDetail}
|
||||
handleEditModule={() => handleEditModule(moduleDetail)}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{modulesView === "gantt_chart" && (
|
||||
<ModulesListGanttChartView modules={modules} mutateModules={mutateModules} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* modules are still loading */}
|
||||
{!modules && (
|
||||
<Loader className="grid grid-cols-3 gap-4 p-8">
|
||||
<Loader.Item height="182px" />
|
||||
<Loader.Item height="182px" />
|
||||
<Loader.Item height="182px" />
|
||||
<Loader.Item height="182px" />
|
||||
<Loader.Item height="182px" />
|
||||
<Loader.Item height="182px" />
|
||||
</Loader>
|
||||
)}
|
||||
|
||||
{/* modules are loaded & there number is 0 */}
|
||||
{modules && modules.length === 0 && (
|
||||
<EmptyState
|
||||
title="Manage your project with modules"
|
||||
description="Modules are smaller, focused projects that help you group and organize issues."
|
||||
image={emptyModule}
|
||||
primaryButton={{
|
||||
icon: <PlusIcon className="h-4 w-4" />,
|
||||
text: "New Module",
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "m",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@ -6,29 +6,26 @@ import useSWR from "swr";
|
||||
|
||||
// layouts
|
||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
|
||||
// hooks
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import modulesService from "services/modules.service";
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CreateUpdateModuleModal, ModulesListGanttChartView, SingleModuleCard } from "components/modules";
|
||||
import { ModuleList } from "components/modules";
|
||||
// ui
|
||||
import { EmptyState, Icon, Loader, PrimaryButton, Tooltip } from "components/ui";
|
||||
import { Icon, PrimaryButton, Tooltip } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// images
|
||||
import emptyModule from "public/empty-state/module.svg";
|
||||
// types
|
||||
import { IModule, SelectModuleType } from "types/modules";
|
||||
import type { NextPage } from "next";
|
||||
// fetch-keys
|
||||
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||
// helper
|
||||
import { replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
|
||||
|
||||
const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
|
||||
const moduleViewOptions = [
|
||||
{
|
||||
type: "gantt_chart",
|
||||
icon: "view_timeline",
|
||||
@ -37,42 +34,31 @@ const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
|
||||
type: "grid",
|
||||
icon: "table_rows",
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
const ProjectModules: NextPage = () => {
|
||||
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
|
||||
const [createUpdateModule, setCreateUpdateModule] = useState(false);
|
||||
|
||||
const [modulesView, setModulesView] = useState<"grid" | "gantt_chart">("grid");
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { user } = useUserAuth();
|
||||
// store
|
||||
const { module: moduleStore } = useMobxStore();
|
||||
|
||||
// states
|
||||
const [modulesView, setModulesView] = useState<"grid" | "gantt_chart">("grid");
|
||||
|
||||
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 ? () => modulesService.getModules(workspaceSlug as string, projectId as string) : null
|
||||
// TODO: remove + "tesings"
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? MODULE_LIST(projectId.toString()) + "tesings" : null,
|
||||
workspaceSlug && projectId ? () => moduleStore.fetchModules(workspaceSlug.toString(), projectId.toString()) : null
|
||||
);
|
||||
|
||||
const handleEditModule = (module: IModule) => {
|
||||
setSelectedModule({ ...module, actionType: "edit" });
|
||||
setCreateUpdateModule(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (createUpdateModule) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setSelectedModule(undefined);
|
||||
clearTimeout(timer);
|
||||
}, 500);
|
||||
}, [createUpdateModule]);
|
||||
const modules = moduleStore.modules[projectId?.toString()!];
|
||||
|
||||
return (
|
||||
<ProjectAuthorizationWrapper
|
||||
@ -114,62 +100,9 @@ const ProjectModules: NextPage = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CreateUpdateModuleModal
|
||||
isOpen={createUpdateModule}
|
||||
setIsOpen={setCreateUpdateModule}
|
||||
data={selectedModule}
|
||||
user={user}
|
||||
/>
|
||||
{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: <PlusIcon 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>
|
||||
)}
|
||||
<ModuleList modulesView={modulesView} modules={modules} />
|
||||
</ProjectAuthorizationWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectModules;
|
||||
export default observer(ProjectModules);
|
||||
|
Loading…
Reference in New Issue
Block a user