plane/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx
sriram veeraghanta 9075f9441c
Refactoring Phase 1 (#199)
* style: added cta at the bottom of sidebar, added missing icons as well, showing dynamic workspace member count on workspace dropdown

* refractor: running parallel request,

made create/edit label function to async function

* fix: sidebar dropdown content going below kanban items

outside click detection in need help dropdown

* refractor: making parallel api calls

fix: create state input comes at bottom, create state input gets on focus automatically, form is getting submitted on enter click

* refactoring file structure and signin page

* style: changed text and added spinner for signing in loading

* refractor: removed unused type

* fix: my issue cta in profile page sending to 404 page

* fix: added new s3 bucket url in next.config.js file

increased image modal height

* packaging UI components

* eslint config

* eslint fixes

* refactoring changes

* build fixes

* minor fixes

* adding todo comments for reference

* refactor: cleared unused imports and re ordered imports

* refactor: removed unused imports

* fix: added workspace argument to useissues hook

* refactor: removed api-routes file, unnecessary constants

* refactor: created helpers folder, removed unnecessary constants

* refactor: new context for issue view

* refactoring issues page

* build fixes

* refactoring

* refactor: create issue modal

* refactor: module ui

* fix: sub-issues mutation

* fix: create more option in create issue modal

* description form debounce issue

* refactor: global component for assignees list

* fix: link module interface

* fix: priority icons and sub-issues count added

* fix: cycle mutation in issue details page

* fix: remove issue from cycle mutation

* fix: create issue modal in home page

* fix: removed unnecessary props

* fix: updated create issue form status

* fix: settings auth breaking

* refactor: issue details page

Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: venkatesh-soulpage <venkatesh.marreboyina@soulpageit.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
2023-01-26 23:42:20 +05:30

313 lines
10 KiB
TypeScript

import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// layouts
import AppLayout from "layouts/app-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// icons
import {
ArrowLeftIcon,
ArrowPathIcon,
ListBulletIcon,
PlusIcon,
} from "@heroicons/react/24/outline";
// components
import CyclesListView from "components/project/cycles/list-view";
import CyclesBoardView from "components/project/cycles/board-view";
import { CreateUpdateIssueModal } from "components/issues";
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
import ExistingIssuesListModal from "components/common/existing-issues-list-modal";
import CycleDetailSidebar from "components/project/cycles/cycle-detail-sidebar";
import View from "components/core/view";
// services
import issuesServices from "services/issues.service";
import cycleServices from "services/cycles.service";
import projectService from "services/project.service";
// ui
import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
// types
import { CycleIssueResponse, IIssue, SelectIssue } from "types";
// fetch-keys
import {
CYCLE_ISSUES,
CYCLE_LIST,
PROJECT_ISSUES_LIST,
PROJECT_MEMBERS,
PROJECT_DETAILS,
} from "constants/fetch-keys";
const SingleCycle: React.FC = () => {
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
const [cycleSidebar, setCycleSidebar] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | null
>(null);
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
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: issues } = useSWR(
workspaceSlug && projectId
? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)
: null,
workspaceSlug && projectId
? () => issuesServices.getIssues(workspaceSlug as string, projectId as string)
: null
);
const { data: cycles } = useSWR(
workspaceSlug && projectId ? CYCLE_LIST(projectId as string) : null,
workspaceSlug && projectId
? () => cycleServices.getCycles(workspaceSlug as string, projectId as string)
: null
);
const { data: cycleIssues } = useSWR<CycleIssueResponse[]>(
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES(cycleId as string) : null,
workspaceSlug && projectId && cycleId
? () =>
cycleServices.getCycleIssues(
workspaceSlug as string,
projectId as string,
cycleId as string
)
: null
);
const cycleIssuesArray = cycleIssues?.map((issue) => ({
...issue.issue_detail,
sub_issues_count: issue.sub_issues_count,
bridge: issue.id,
}));
const { data: members } = useSWR(
workspaceSlug && projectId ? PROJECT_MEMBERS(workspaceSlug as string) : null,
workspaceSlug && projectId
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
: null,
{
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
if (err?.status === 403) return;
setTimeout(() => revalidate(revalidateOpts), 5000);
},
}
);
const partialUpdateIssue = (formData: Partial<IIssue>, issueId: string) => {
if (!workspaceSlug || !projectId) return;
issuesServices
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData)
.then(() => {
mutate(CYCLE_ISSUES(cycleId as string));
})
.catch((error) => {
console.log(error);
});
};
const openCreateIssueModal = (
issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create"
) => {
if (issue) {
setPreloadedData(null);
setSelectedIssues({ ...issue, actionType });
} else setSelectedIssues(null);
setIsIssueModalOpen(true);
};
const openIssuesListModal = () => {
setCycleIssuesListModal(true);
};
const handleAddIssuesToCycle = async (data: { issues: string[] }) => {
if (workspaceSlug && projectId) {
await issuesServices
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, data)
.then((res) => {
console.log(res);
mutate(CYCLE_ISSUES(cycleId as string));
})
.catch((e) => {
console.log(e);
});
}
};
const removeIssueFromCycle = (bridgeId: string) => {
if (!workspaceSlug || !projectId) return;
mutate<CycleIssueResponse[]>(
CYCLE_ISSUES(cycleId as string),
(prevData) => prevData?.filter((p) => p.id !== bridgeId),
false
);
issuesServices
.removeIssueFromCycle(
workspaceSlug as string,
projectId as string,
cycleId as string,
bridgeId
)
.then((res) => {
console.log(res);
})
.catch((e) => {
console.log(e);
});
};
return (
<IssueViewContextProvider>
<CreateUpdateIssueModal
isOpen={isIssueModalOpen && selectedIssues?.actionType !== "delete"}
data={selectedIssues}
prePopulateData={
preloadedData
? { cycle: cycleId as string, ...preloadedData }
: { cycle: cycleId as string, ...selectedIssues }
}
handleClose={() => setIsIssueModalOpen(false)}
/>
<ExistingIssuesListModal
isOpen={cycleIssuesListModal}
handleClose={() => setCycleIssuesListModal(false)}
type="cycle"
issues={issues?.results.filter((i) => !i.issue_cycle) ?? []}
handleOnSubmit={handleAddIssuesToCycle}
/>
<ConfirmIssueDeletion
handleClose={() => setDeleteIssue(undefined)}
isOpen={!!deleteIssue}
data={issues?.results.find((issue) => issue.id === deleteIssue)}
/>
<AppLayout
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem
title={`${activeProject?.name ?? "Project"} Cycles`}
link={`/${workspaceSlug}/projects/${activeProject?.id}/cycles`}
/>
</Breadcrumbs>
}
left={
<CustomMenu
label={
<>
<ArrowPathIcon className="h-3 w-3" />
{cycles?.find((c) => c.id === cycleId)?.name}
</>
}
className="ml-1.5"
width="auto"
>
{cycles?.map((cycle) => (
<CustomMenu.MenuItem
key={cycle.id}
renderAs="a"
href={`/${workspaceSlug}/projects/${activeProject?.id}/cycles/${cycle.id}`}
>
{cycle.name}
</CustomMenu.MenuItem>
))}
</CustomMenu>
}
right={
<div
className={`flex items-center gap-2 ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}
>
<View issues={cycleIssuesArray ?? []} />
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-100 ${
cycleSidebar ? "rotate-180" : ""
}`}
onClick={() => setCycleSidebar((prevData) => !prevData)}
>
<ArrowLeftIcon className="h-4 w-4" />
</button>
</div>
}
>
{cycleIssuesArray ? (
cycleIssuesArray.length > 0 ? (
<div className={`h-full ${cycleSidebar ? "mr-[24rem]" : ""} duration-300`}>
<CyclesListView
issues={cycleIssuesArray ?? []}
openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal}
removeIssueFromCycle={removeIssueFromCycle}
setPreloadedData={setPreloadedData}
/>
<CyclesBoardView
issues={cycleIssuesArray ?? []}
removeIssueFromCycle={removeIssueFromCycle}
members={members}
openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData}
/>
</div>
) : (
<div
className={`flex h-full flex-col items-center justify-center px-4 ${
cycleSidebar ? "mr-[24rem]" : ""
} duration-300`}
>
<EmptySpace
title="You don't have any issue yet."
description="A cycle is a fixed time period where a team commits to a set number of issues from their backlog. Cycles are usually one, two, or four weeks long."
Icon={ArrowPathIcon}
>
<EmptySpaceItem
title="Create a new issue"
description="Click to create a new issue inside the cycle."
Icon={PlusIcon}
action={() => openCreateIssueModal()}
/>
<EmptySpaceItem
title="Add an existing issue"
description="Open list"
Icon={ListBulletIcon}
action={() => openIssuesListModal()}
/>
</EmptySpace>
</div>
)
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
)}
<CycleDetailSidebar
cycle={cycles?.find((c) => c.id === (cycleId as string))}
isOpen={cycleSidebar}
cycleIssues={cycleIssues ?? []}
/>
</AppLayout>
</IssueViewContextProvider>
);
};
export default SingleCycle;