forked from github/plane
chore: archived issues restructure. (#3469)
* chore: archived issues restructure. * fix issue detail functionalities --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
parent
f8208b1b5e
commit
60b5589c48
@ -1,43 +1,22 @@
|
||||
import { useCallback, useEffect, useState, ReactElement } from "react";
|
||||
import { useState, ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { useForm } from "react-hook-form";
|
||||
// services
|
||||
import { IssueService, IssueArchiveService } from "services/issue";
|
||||
import useSWR from "swr";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useIssueDetail, useIssues, useProject } from "hooks/store";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
// FIXME: have to replace this once the issue details page is ready --issue-detail--
|
||||
// import { IssueDetailsSidebar, IssueMainContent } from "components/issues";
|
||||
import { IssueDetailRoot } from "components/issues";
|
||||
import { ProjectArchivedIssueDetailsHeader } from "components/headers";
|
||||
// ui
|
||||
import { ArchiveIcon, Loader } from "@plane/ui";
|
||||
// icons
|
||||
import { History } from "lucide-react";
|
||||
// types
|
||||
import { TIssue } from "@plane/types";
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
import { useProject } from "hooks/store";
|
||||
|
||||
const defaultValues: Partial<TIssue> = {
|
||||
name: "",
|
||||
// description: "",
|
||||
description_html: "",
|
||||
estimate_point: null,
|
||||
state_id: "",
|
||||
priority: "low",
|
||||
target_date: new Date().toString(),
|
||||
cycle_id: null,
|
||||
module_id: null,
|
||||
};
|
||||
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
const issueArchiveService = new IssueArchiveService();
|
||||
// constants
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
|
||||
// router
|
||||
@ -46,84 +25,43 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
|
||||
// states
|
||||
const [isRestoring, setIsRestoring] = useState(false);
|
||||
// hooks
|
||||
const {
|
||||
fetchIssue,
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
const {
|
||||
issues: { removeIssueFromArchived },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
const { setToastAlert } = useToast();
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { data: issueDetails, mutate: mutateIssueDetails } = useSWR<TIssue | undefined>(
|
||||
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? () =>
|
||||
issueArchiveService.retrieveArchivedIssue(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
archivedIssueId as string
|
||||
)
|
||||
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
|
||||
: null,
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString(), true)
|
||||
: null
|
||||
);
|
||||
|
||||
const { reset, control, watch } = useForm<TIssue>({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const submitChanges = useCallback(
|
||||
async (formData: Partial<TIssue>) => {
|
||||
if (!workspaceSlug || !projectId || !archivedIssueId) return;
|
||||
|
||||
mutate<TIssue>(
|
||||
ISSUE_DETAILS(archivedIssueId as string),
|
||||
(prevData) => {
|
||||
if (!prevData) return prevData;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
...formData,
|
||||
};
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
const payload: Partial<TIssue> = {
|
||||
...formData,
|
||||
};
|
||||
|
||||
await issueService
|
||||
.patchIssue(workspaceSlug as string, projectId as string, archivedIssueId as string, payload)
|
||||
.then(() => {
|
||||
mutateIssueDetails();
|
||||
mutate(PROJECT_ISSUES_ACTIVITY(archivedIssueId as string));
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
},
|
||||
[workspaceSlug, archivedIssueId, projectId, mutateIssueDetails]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!issueDetails) return;
|
||||
|
||||
mutate(PROJECT_ISSUES_ACTIVITY(archivedIssueId as string));
|
||||
reset({
|
||||
...issueDetails,
|
||||
});
|
||||
}, [issueDetails, reset, archivedIssueId]);
|
||||
const issue = getIssueById(archivedIssueId?.toString() || "") || undefined;
|
||||
if (!issue) return <></>;
|
||||
|
||||
const handleUnArchive = async () => {
|
||||
if (!workspaceSlug || !projectId || !archivedIssueId) return;
|
||||
|
||||
setIsRestoring(true);
|
||||
|
||||
await issueArchiveService
|
||||
.unarchiveIssue(workspaceSlug as string, projectId as string, archivedIssueId as string)
|
||||
await removeIssueFromArchived(workspaceSlug as string, projectId as string, archivedIssueId as string)
|
||||
.then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success",
|
||||
message:
|
||||
issueDetails &&
|
||||
`${getProjectById(issueDetails.project_id)?.identifier}-${
|
||||
issueDetails?.sequence_id
|
||||
} is restored successfully under the project ${getProjectById(issueDetails.project_id)?.name}`,
|
||||
issue &&
|
||||
`${getProjectById(issue.project_id)?.identifier}-${
|
||||
issue?.sequence_id
|
||||
} is restored successfully under the project ${getProjectById(issue.project_id)?.name}`,
|
||||
});
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${archivedIssueId}`);
|
||||
})
|
||||
@ -137,45 +75,11 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
|
||||
.finally(() => setIsRestoring(false));
|
||||
};
|
||||
|
||||
const issueLoader = !issue || isLoading ? true : false;
|
||||
|
||||
return (
|
||||
<>
|
||||
{issueDetails && projectId ? (
|
||||
<div className="flex h-full overflow-hidden">
|
||||
<div className="h-full w-2/3 space-y-2 divide-y-2 divide-custom-border-300 overflow-y-auto p-5">
|
||||
{issueDetails.archived_at && (
|
||||
<div className="flex items-center justify-between gap-2 rounded-md border border-custom-border-200 bg-custom-background-90 px-2.5 py-2 text-sm text-custom-text-200">
|
||||
<div className="flex items-center gap-2">
|
||||
<ArchiveIcon className="h-3.5 w-3.5" />
|
||||
<p>This issue has been archived by Plane.</p>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center gap-2 rounded-md border border-custom-border-200 p-1.5 text-sm"
|
||||
onClick={handleUnArchive}
|
||||
disabled={isRestoring}
|
||||
>
|
||||
<History className="h-3.5 w-3.5" />
|
||||
|
||||
<span>{isRestoring ? "Restoring..." : "Restore Issue"}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* FIXME: have to replace this once the issue details page is ready --issue-detail-- */}
|
||||
{/* <div className="pointer-events-none space-y-5 divide-y-2 divide-custom-border-200 opacity-60">
|
||||
<IssueMainContent issueDetails={issueDetails} submitChanges={submitChanges} uneditable />
|
||||
</div> */}
|
||||
</div>
|
||||
{/* FIXME: have to replace this once the issue details page is ready --issue-detail-- */}
|
||||
{/* <div className="h-full w-1/3 space-y-5 overflow-hidden border-l border-custom-border-300 p-5">
|
||||
<IssueDetailsSidebar
|
||||
control={control}
|
||||
issueDetail={issueDetails}
|
||||
submitChanges={submitChanges}
|
||||
watch={watch}
|
||||
uneditable
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
) : (
|
||||
{issueLoader ? (
|
||||
<Loader className="flex h-full gap-5 p-5">
|
||||
<div className="basis-2/3 space-y-2">
|
||||
<Loader.Item height="30px" width="40%" />
|
||||
@ -190,6 +94,35 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
|
||||
<Loader.Item height="30px" />
|
||||
</div>
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="flex h-full overflow-hidden">
|
||||
<div className="h-full w-full space-y-2 divide-y-2 divide-custom-border-300 overflow-y-auto p-5">
|
||||
{issue?.archived_at && (
|
||||
<div className="flex items-center justify-between gap-2 rounded-md border border-custom-border-200 bg-custom-background-90 px-2.5 py-2 text-sm text-custom-text-200">
|
||||
<div className="flex items-center gap-2">
|
||||
<ArchiveIcon className="h-3.5 w-3.5" />
|
||||
<p>This issue has been archived by Plane.</p>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center gap-2 rounded-md border border-custom-border-200 p-1.5 text-sm"
|
||||
onClick={handleUnArchive}
|
||||
disabled={isRestoring}
|
||||
>
|
||||
<History className="h-3.5 w-3.5" />
|
||||
<span>{isRestoring ? "Restoring..." : "Restore Issue"}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{workspaceSlug && projectId && archivedIssueId && (
|
||||
<IssueDetailRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
issueId={archivedIssueId.toString()}
|
||||
is_archived
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -59,7 +59,6 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
issueId={issueId.toString()}
|
||||
is_archived={!!issue?.archived_at}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
@ -128,8 +128,8 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues
|
||||
try {
|
||||
const response = await this.archivedIssueService.unarchiveIssue(workspaceSlug, projectId, issueId);
|
||||
|
||||
const issueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === issueId);
|
||||
if (issueIndex >= 0)
|
||||
const issueIndex = this.issues[projectId]?.findIndex((_issueId) => _issueId === issueId);
|
||||
if (issueIndex && issueIndex >= 0)
|
||||
runInAction(() => {
|
||||
this.issues[projectId].splice(issueIndex, 1);
|
||||
});
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { makeObservable } from "mobx";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
import { IssueArchiveService, IssueService } from "services/issue";
|
||||
// types
|
||||
import { IIssueDetail } from "./root.store";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export interface IIssueStoreActions {
|
||||
// actions
|
||||
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string, isArchived?: boolean) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<TIssue>;
|
||||
@ -31,6 +31,7 @@ export class IssueStore implements IIssueStore {
|
||||
rootIssueDetailStore: IIssueDetail;
|
||||
// services
|
||||
issueService;
|
||||
issueArchiveService;
|
||||
|
||||
constructor(rootStore: IIssueDetail) {
|
||||
makeObservable(this, {});
|
||||
@ -38,6 +39,7 @@ export class IssueStore implements IIssueStore {
|
||||
this.rootIssueDetailStore = rootStore;
|
||||
// services
|
||||
this.issueService = new IssueService();
|
||||
this.issueArchiveService = new IssueArchiveService();
|
||||
}
|
||||
|
||||
// helper methods
|
||||
@ -47,12 +49,17 @@ export class IssueStore implements IIssueStore {
|
||||
};
|
||||
|
||||
// actions
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, isArchived = false) => {
|
||||
try {
|
||||
const query = {
|
||||
expand: "state,assignees,labels,parent",
|
||||
};
|
||||
const issue = (await this.issueService.retrieve(workspaceSlug, projectId, issueId, query)) as any;
|
||||
|
||||
let issue: any;
|
||||
|
||||
if (isArchived) issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId);
|
||||
else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
|
||||
|
||||
if (!issue) throw new Error("Issue not found");
|
||||
|
||||
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issue]);
|
||||
|
@ -133,8 +133,8 @@ export class IssueDetail implements IIssueDetail {
|
||||
toggleRelationModal = (value: TIssueRelationTypes | null) => (this.isRelationModalOpen = value);
|
||||
|
||||
// issue
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||
this.issue.fetchIssue(workspaceSlug, projectId, issueId);
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, isArchived = false) =>
|
||||
this.issue.fetchIssue(workspaceSlug, projectId, issueId, isArchived);
|
||||
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) =>
|
||||
this.issue.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||
|
Loading…
Reference in New Issue
Block a user