[WEB-4828] refactor: remove legacy project-level draft work items components (#7694)
This commit is contained in:
parent
fd5ba6c7b8
commit
d960d7ce88
38 changed files with 16 additions and 1313 deletions
|
|
@ -1,178 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane constants
|
||||
import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
// ui
|
||||
import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { isIssueFilterActive } from "@plane/utils";
|
||||
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||
import {
|
||||
DisplayFiltersSelection,
|
||||
FiltersDropdown,
|
||||
FilterSelection,
|
||||
LayoutSelection,
|
||||
} from "@/components/issues/issue-layouts/filters";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useLabel } from "@/hooks/store/use-label";
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useProjectState } from "@/hooks/store/use-project-state";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs/project";
|
||||
|
||||
// FIXME: Deprecated. Remove it
|
||||
export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.DRAFT);
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { projectStates } = useProjectState();
|
||||
const { projectLabels } = useLabel();
|
||||
const {
|
||||
project: { projectMemberIds },
|
||||
} = useMember();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||
|
||||
const handleFiltersUpdate = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
// this validation is majorly for the filter start_date, target_date custom
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else newValues.splice(newValues.indexOf(val), 1);
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
|
||||
},
|
||||
[workspaceSlug, projectId, issueFilters, updateFilters]
|
||||
);
|
||||
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: EIssueLayoutTypes) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
|
||||
},
|
||||
[workspaceSlug, projectId, updateFilters]
|
||||
);
|
||||
|
||||
const handleDisplayFilters = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
|
||||
},
|
||||
[workspaceSlug, projectId, updateFilters]
|
||||
);
|
||||
|
||||
const handleDisplayProperties = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
|
||||
},
|
||||
[workspaceSlug, projectId, updateFilters]
|
||||
);
|
||||
|
||||
const issueCount = undefined;
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-header w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
|
||||
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Breadcrumbs isLoading={loader === "init-loader"}>
|
||||
<ProjectBreadcrumb workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
|
||||
<Breadcrumbs.Item
|
||||
component={
|
||||
<BreadcrumbLink
|
||||
label="Draft work items"
|
||||
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
{issueCount && issueCount > 0 ? (
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's draft`}
|
||||
position="bottom"
|
||||
>
|
||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
{issueCount}
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<LayoutSelection
|
||||
layouts={[EIssueLayoutTypes.LIST, EIssueLayoutTypes.KANBAN]}
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
states={projectStates}
|
||||
cycleViewDisabled={!currentProjectDetails?.cycle_view}
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
cycleViewDisabled={!currentProjectDetails?.cycle_view}
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
"use client";
|
||||
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectDraftIssueHeader } from "./header";
|
||||
|
||||
export default function ProjectDraftIssuesLayou({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectDraftIssueHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { X, PenSquare } from "lucide-react";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { DraftIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/draft-issue-layout-root";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
const ProjectDraftIssuesPage = observer(() => {
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store
|
||||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Draft work items` : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="gap-1 flex items-center border-b border-custom-border-200 px-4 py-2.5 shadow-sm bg-custom-background-100 z-[12]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/`)}
|
||||
className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs"
|
||||
>
|
||||
<PenSquare className="h-4 w-4" />
|
||||
<span>Draft work items</span>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
<DraftIssueLayoutRoot />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default ProjectDraftIssuesPage;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EIssueServiceType, EIssuesStoreType, TIssue } from "@plane/types";
|
||||
// components
|
||||
|
|
@ -22,7 +22,6 @@ export type TIssueLevelModalsProps = {
|
|||
export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) => {
|
||||
const { projectId, issueId } = props;
|
||||
// router
|
||||
const pathname = usePathname();
|
||||
const { workspaceSlug, cycleId, moduleId } = useParams();
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
|
|
@ -45,7 +44,6 @@ export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) =>
|
|||
} = useCommandPalette();
|
||||
// derived values
|
||||
const issueDetails = issueId ? getIssueById(issueId) : undefined;
|
||||
const isDraftIssue = pathname?.includes("draft-issues") || false;
|
||||
const { fetchSubIssues: fetchSubWorkItems } = useIssueDetail();
|
||||
const { fetchSubIssues: fetchEpicSubWorkItems } = useIssueDetail(EIssueServiceType.EPICS);
|
||||
|
||||
|
|
@ -81,7 +79,6 @@ export const IssueLevelModals: FC<TIssueLevelModalsProps> = observer((props) =>
|
|||
isOpen={isCreateIssueModalOpen}
|
||||
onClose={() => toggleCreateIssueModal(false)}
|
||||
data={getCreateIssueModalData()}
|
||||
isDraft={isDraftIssue}
|
||||
onSubmit={handleCreateIssueSubmit}
|
||||
allowedProjectIds={createWorkItemAllowedProjectIds}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import { observer } from "mobx-react";
|
||||
|
||||
// FIXME: Project drafts is deprecated. Remove this component and all the related code.
|
||||
export const ProjectDraftEmptyState: React.FC = observer(() => (
|
||||
<div className="relative h-full w-full overflow-y-auto" />
|
||||
));
|
||||
|
|
@ -5,7 +5,6 @@ import { TeamProjectWorkItemEmptyState } from "@/plane-web/components/issues/iss
|
|||
// components
|
||||
import { ProjectArchivedEmptyState } from "./archived-issues";
|
||||
import { CycleEmptyState } from "./cycle";
|
||||
import { ProjectDraftEmptyState } from "./draft-issues";
|
||||
import { GlobalViewEmptyState } from "./global-view";
|
||||
import { ModuleEmptyState } from "./module";
|
||||
import { ProfileViewEmptyState } from "./profile-view";
|
||||
|
|
@ -29,8 +28,6 @@ export const IssueLayoutEmptyState = (props: Props) => {
|
|||
return <CycleEmptyState />;
|
||||
case EIssuesStoreType.MODULE:
|
||||
return <ModuleEmptyState />;
|
||||
case EIssuesStoreType.DRAFT:
|
||||
return <ProjectDraftEmptyState />;
|
||||
case EIssuesStoreType.GLOBAL:
|
||||
return <GlobalViewEmptyState />;
|
||||
case EIssuesStoreType.PROFILE:
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EIssueFilterType } from "@plane/constants";
|
||||
import { EIssuesStoreType, IIssueFilterOptions } from "@plane/types";
|
||||
// hooks
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { useLabel } from "@/hooks/store/use-label";
|
||||
import { useProjectState } from "@/hooks/store/use-project-state";
|
||||
// local imports
|
||||
import { AppliedFiltersList } from "../filters-list";
|
||||
|
||||
export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.DRAFT);
|
||||
const { projectLabels } = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||
if (!value) return;
|
||||
if (Array.isArray(value) && value.length === 0) return;
|
||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||
});
|
||||
|
||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
// remove all values of the key if value is null
|
||||
if (!value) {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the passed value from the key
|
||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const newFilters: IIssueFilterOptions = {};
|
||||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = [];
|
||||
});
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters });
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex justify-between p-4 gap-2.5">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
states={projectStates}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -34,7 +34,6 @@ export type KanbanStoreType =
|
|||
| EIssuesStoreType.MODULE
|
||||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.DRAFT
|
||||
| EIssuesStoreType.PROFILE
|
||||
| EIssuesStoreType.TEAM
|
||||
| EIssuesStoreType.TEAM_VIEW
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// lucide icons
|
||||
import { Minimize2, Maximize2, Circle, Plus } from "lucide-react";
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
|
|
@ -58,9 +58,6 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
const storeType = useIssueStoreType();
|
||||
// router
|
||||
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
||||
const pathname = usePathname();
|
||||
|
||||
const isDraftIssue = pathname.includes("draft-issue");
|
||||
|
||||
const renderExistingIssueModal = moduleId || cycleId;
|
||||
const ExistingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true };
|
||||
|
|
@ -97,7 +94,6 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
onClose={() => setIsOpen(false)}
|
||||
data={issuePayload}
|
||||
storeType={storeType}
|
||||
isDraft={isDraftIssue}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import { observer } from "mobx-react";
|
||||
// local imports
|
||||
import { DraftIssueQuickActions } from "../../quick-action-dropdowns";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
|
||||
export const DraftKanBanLayout: React.FC = observer(() => <BaseKanBanRoot QuickActions={DraftIssueQuickActions} />);
|
||||
|
|
@ -30,7 +30,6 @@ type ListStoreType =
|
|||
| EIssuesStoreType.MODULE
|
||||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.DRAFT
|
||||
| EIssuesStoreType.PROFILE
|
||||
| EIssuesStoreType.ARCHIVED
|
||||
| EIssuesStoreType.WORKSPACE_DRAFT
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { CircleDashed, Plus } from "lucide-react";
|
||||
// types
|
||||
import { WORK_ITEM_TRACKER_EVENTS } from "@plane/constants";
|
||||
|
|
@ -58,10 +58,8 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug, projectId, moduleId, cycleId } = useParams();
|
||||
const pathname = usePathname();
|
||||
const storeType = useIssueStoreType();
|
||||
// derived values
|
||||
const isDraftIssue = pathname.includes("draft-issue");
|
||||
const renderExistingIssueModal = moduleId || cycleId;
|
||||
const existingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true };
|
||||
const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(groupID) === "empty";
|
||||
|
|
@ -167,7 +165,6 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
onClose={() => setIsOpen(false)}
|
||||
data={issuePayload}
|
||||
storeType={storeType}
|
||||
isDraft={isDraftIssue}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// local imports
|
||||
import { DraftIssueQuickActions } from "../../quick-action-dropdowns";
|
||||
import { BaseListRoot } from "../base-list-root";
|
||||
|
||||
export const DraftIssueListLayout: FC = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
||||
if (!workspaceSlug || !projectId) return null;
|
||||
|
||||
return <BaseListRoot QuickActions={DraftIssueQuickActions} />;
|
||||
});
|
||||
|
|
@ -118,7 +118,6 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
|
|||
if (issueToEdit && handleUpdate) await handleUpdate(data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.GLOBAL}
|
||||
isDraft={false}
|
||||
/>
|
||||
{issue.project_id && workspaceSlug && (
|
||||
<DuplicateWorkItemModal
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||
if (issueToEdit && handleUpdate) await handleUpdate(data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.CYCLE}
|
||||
isDraft={false}
|
||||
/>
|
||||
{issue.project_id && workspaceSlug && (
|
||||
<DuplicateWorkItemModal
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import omit from "lodash/omit";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EIssuesStoreType, TIssue } from "@plane/types";
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
// local imports
|
||||
import { DeleteIssueModal } from "../../delete-issue-modal";
|
||||
import { CreateUpdateIssueModal } from "../../issue-modal/modal";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
import { useDraftIssueMenuItems, MenuItemFactoryProps } from "./helper";
|
||||
|
||||
export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((props) => {
|
||||
const {
|
||||
issue,
|
||||
handleDelete,
|
||||
handleUpdate,
|
||||
customActionButton,
|
||||
portalElement,
|
||||
readOnly = false,
|
||||
placements = "bottom-end",
|
||||
parentRef,
|
||||
} = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// states
|
||||
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||
const [issueToEdit, setIssueToEdit] = useState<TIssue | undefined>(undefined);
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const activeLayout = "Draft Issues";
|
||||
// auth
|
||||
const isEditingAllowed =
|
||||
allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT,
|
||||
workspaceSlug?.toString(),
|
||||
issue.project_id ?? undefined
|
||||
) && !readOnly;
|
||||
const isDeletingAllowed = isEditingAllowed;
|
||||
|
||||
const isDraftIssue = pathname?.includes("draft-issues") || false;
|
||||
|
||||
const duplicateIssuePayload = omit(
|
||||
{
|
||||
...issue,
|
||||
name: `${issue.name} (copy)`,
|
||||
is_draft: isDraftIssue ? false : issue.is_draft,
|
||||
sourceIssueId: issue.id,
|
||||
},
|
||||
["id"]
|
||||
);
|
||||
|
||||
// Menu items and modals using helper
|
||||
const menuItemProps: MenuItemFactoryProps = {
|
||||
issue,
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
activeLayout,
|
||||
isEditingAllowed,
|
||||
isDeletingAllowed,
|
||||
isDraftIssue,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
handleDelete,
|
||||
handleUpdate,
|
||||
storeType: EIssuesStoreType.DRAFT,
|
||||
};
|
||||
|
||||
const MENU_ITEMS = useDraftIssueMenuItems(menuItemProps);
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.DRAFT });
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modals */}
|
||||
<DeleteIssueModal
|
||||
data={issue}
|
||||
isOpen={deleteIssueModal}
|
||||
handleClose={() => setDeleteIssueModal(false)}
|
||||
onSubmit={handleDelete}
|
||||
/>
|
||||
<CreateUpdateIssueModal
|
||||
isOpen={createUpdateIssueModal}
|
||||
onClose={() => {
|
||||
setCreateUpdateIssueModal(false);
|
||||
setIssueToEdit(undefined);
|
||||
}}
|
||||
data={issueToEdit ?? duplicateIssuePayload}
|
||||
onSubmit={async (data) => {
|
||||
if (issueToEdit && handleUpdate) await handleUpdate(data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.DRAFT}
|
||||
isDraft={isDraftIssue}
|
||||
/>
|
||||
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
placement={placements}
|
||||
customButton={customActionButton}
|
||||
portalElement={portalElement}
|
||||
menuItemsClassName="z-[14]"
|
||||
maxHeight="lg"
|
||||
closeOnSelect
|
||||
>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
return (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.key}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({ elementName: WORK_ITEM_TRACKER_ELEMENTS.QUICK_ACTIONS.DRAFT });
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-2",
|
||||
{
|
||||
"text-custom-text-400": item.disabled,
|
||||
},
|
||||
item.className
|
||||
)}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.icon && <item.icon className={cn("h-3 w-3", item.iconClassName)} />}
|
||||
<div>
|
||||
<h5>{item.title}</h5>
|
||||
{item.description && (
|
||||
<p
|
||||
className={cn("text-custom-text-300 whitespace-pre-line", {
|
||||
"text-custom-text-400": item.disabled,
|
||||
})}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
})}
|
||||
</CustomMenu>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -54,7 +54,6 @@ export interface MenuItemFactoryProps {
|
|||
isRestoringAllowed?: boolean;
|
||||
isInArchivableGroup?: boolean;
|
||||
issueTypeDetail?: { is_active?: boolean };
|
||||
isDraftIssue?: boolean;
|
||||
// Action handlers
|
||||
setIssueToEdit: (issue: TIssue | undefined) => void;
|
||||
setCreateUpdateIssueModal: (open: boolean) => void;
|
||||
|
|
@ -369,9 +368,3 @@ export const useArchivedIssueMenuItems = (props: MenuItemFactoryProps): TContext
|
|||
[factory]
|
||||
);
|
||||
};
|
||||
|
||||
export const useDraftIssueMenuItems = (props: MenuItemFactoryProps): TContextMenuItem[] => {
|
||||
const factory = useMenuItemFactory(props);
|
||||
|
||||
return useMemo(() => [factory.createEditMenuItem(), factory.createDeleteMenuItem()], [factory]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export * from "./all-issue";
|
||||
export * from "./archived-issue";
|
||||
export * from "./cycle-issue";
|
||||
export * from "./draft-issue";
|
||||
export * from "./module-issue";
|
||||
export * from "./project-issue";
|
||||
export * from "./helper";
|
||||
|
|
|
|||
|
|
@ -88,13 +88,10 @@ export const WorkItemDetailQuickActions: React.FC<TWorkItemDetailQuickActionProp
|
|||
|
||||
const isDeletingAllowed = isEditingAllowed;
|
||||
|
||||
const isDraftIssue = pathname?.includes("draft-issues") || false;
|
||||
|
||||
const duplicateIssuePayload = omit(
|
||||
{
|
||||
...issue,
|
||||
name: `${issue.name} (copy)`,
|
||||
is_draft: isDraftIssue ? false : issue.is_draft,
|
||||
sourceIssueId: issue.id,
|
||||
},
|
||||
["id"]
|
||||
|
|
@ -137,7 +134,6 @@ export const WorkItemDetailQuickActions: React.FC<TWorkItemDetailQuickActionProp
|
|||
isRestoringAllowed,
|
||||
isDeletingAllowed,
|
||||
isInArchivableGroup,
|
||||
isDraftIssue,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal: customEditAction,
|
||||
setDeleteIssueModal: customDeleteAction,
|
||||
|
|
@ -220,7 +216,6 @@ export const WorkItemDetailQuickActions: React.FC<TWorkItemDetailQuickActionProp
|
|||
if (issueToEdit && handleUpdate) await handleUpdate(data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.PROJECT}
|
||||
isDraft={isDraftIssue}
|
||||
fetchIssueDetails={false}
|
||||
/>
|
||||
{issue.project_id && workspaceSlug && (
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
|
|||
if (issueToEdit && handleUpdate) await handleUpdate(data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.MODULE}
|
||||
isDraft={false}
|
||||
/>
|
||||
{issue.project_id && workspaceSlug && (
|
||||
<DuplicateWorkItemModal
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useState } from "react";
|
||||
import omit from "lodash/omit";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import {
|
||||
ARCHIVABLE_STATE_GROUPS,
|
||||
|
|
@ -43,7 +43,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
} = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// states
|
||||
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||
const [issueToEdit, setIssueToEdit] = useState<TIssue | undefined>(undefined);
|
||||
|
|
@ -71,13 +70,10 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
const isInArchivableGroup = !!stateDetails && ARCHIVABLE_STATE_GROUPS.includes(stateDetails?.group);
|
||||
const isDeletingAllowed = isEditingAllowed;
|
||||
|
||||
const isDraftIssue = pathname?.includes("draft-issues") || false;
|
||||
|
||||
const duplicateIssuePayload = omit(
|
||||
{
|
||||
...issue,
|
||||
name: `${issue.name} (copy)`,
|
||||
is_draft: isDraftIssue ? false : issue.is_draft,
|
||||
sourceIssueId: issue.id,
|
||||
},
|
||||
["id"]
|
||||
|
|
@ -93,7 +89,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
isArchivingAllowed,
|
||||
isDeletingAllowed,
|
||||
isInArchivableGroup,
|
||||
isDraftIssue,
|
||||
setIssueToEdit,
|
||||
setCreateUpdateIssueModal,
|
||||
setDeleteIssueModal,
|
||||
|
|
@ -141,7 +136,6 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
|
|||
if (issueToEdit && handleUpdate) await handleUpdate(data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.PROJECT}
|
||||
isDraft={isDraftIssue}
|
||||
/>
|
||||
{issue.project_id && workspaceSlug && (
|
||||
<DuplicateWorkItemModal
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane constants
|
||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||
// hooks
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
import { IssuesStoreContext } from "@/hooks/use-issue-layout-store";
|
||||
// components
|
||||
import { DraftIssueAppliedFiltersRoot } from "../filters/applied-filters/roots/draft-issue";
|
||||
import { DraftKanBanLayout } from "../kanban/roots/draft-issue-root";
|
||||
import { DraftIssueListLayout } from "../list/roots/draft-issue-root";
|
||||
// ui
|
||||
// constants
|
||||
|
||||
const DraftIssueLayout = (props: { activeLayout: EIssueLayoutTypes | undefined }) => {
|
||||
switch (props.activeLayout) {
|
||||
case EIssueLayoutTypes.LIST:
|
||||
return <DraftIssueListLayout />;
|
||||
case EIssueLayoutTypes.KANBAN:
|
||||
return <DraftKanBanLayout />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export const DraftIssueLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// hooks
|
||||
const { issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
|
||||
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `DRAFT_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
},
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
|
||||
const issueFilters = issuesFilter?.getIssueFilters(projectId?.toString());
|
||||
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
if (isLoading && !issueFilters)
|
||||
return (
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<LogoSpinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<IssuesStoreContext.Provider value={EIssuesStoreType.DRAFT}>
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
<DraftIssueAppliedFiltersRoot />
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
<DraftIssueLayout activeLayout={activeLayout} />
|
||||
{/* issue peek overview */}
|
||||
<IssuePeekOverview is_draft />
|
||||
</div>
|
||||
</div>
|
||||
</IssuesStoreContext.Provider>
|
||||
);
|
||||
});
|
||||
|
|
@ -86,12 +86,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
setDescription(data?.description_html || "<p></p>");
|
||||
return;
|
||||
}
|
||||
const response = await fetchIssue(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issueId,
|
||||
isDraft ? "DRAFT" : "DEFAULT"
|
||||
);
|
||||
const response = await fetchIssue(workspaceSlug.toString(), projectId.toString(), issueId);
|
||||
if (response) setDescription(response?.description_html || "<p></p>");
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export const IssuePeekOverview: FC<IWorkItemPeekOverview> = observer((props) =>
|
|||
fetch: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
setError(false);
|
||||
await fetchIssue(workspaceSlug, projectId, issueId, is_draft ? "DRAFT" : "DEFAULT");
|
||||
await fetchIssue(workspaceSlug, projectId, issueId);
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
console.error("Error fetching the parent issue", error);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export const SidebarQuickActions = observer(() => {
|
|||
onClose={() => setIsDraftIssueModalOpen(false)}
|
||||
data={workspaceDraftIssue ?? {}}
|
||||
onSubmit={() => removeWorkspaceDraftIssue()}
|
||||
fetchIssueDetails={false}
|
||||
isDraft
|
||||
/>
|
||||
<div className={cn("flex items-center justify-between gap-1 cursor-pointer", {})}>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import type { ITeamViewIssues, ITeamViewIssuesFilter } from "@/plane-web/store/i
|
|||
import type { IWorkspaceIssues } from "@/plane-web/store/issue/workspace/issue.store";
|
||||
import type { IArchivedIssues, IArchivedIssuesFilter } from "@/store/issue/archived";
|
||||
import type { ICycleIssues, ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import type { IDraftIssues, IDraftIssuesFilter } from "@/store/issue/draft";
|
||||
import type { IModuleIssues, IModuleIssuesFilter } from "@/store/issue/module";
|
||||
import type { IProfileIssues, IProfileIssuesFilter } from "@/store/issue/profile";
|
||||
import type { IProjectIssues, IProjectIssuesFilter } from "@/store/issue/project";
|
||||
|
|
@ -65,10 +64,6 @@ export type TStoreIssues = {
|
|||
issues: IArchivedIssues;
|
||||
issuesFilter: IArchivedIssuesFilter;
|
||||
};
|
||||
[EIssuesStoreType.DRAFT]: defaultIssueStore & {
|
||||
issues: IDraftIssues;
|
||||
issuesFilter: IDraftIssuesFilter;
|
||||
};
|
||||
[EIssuesStoreType.DEFAULT]: defaultIssueStore & {
|
||||
issues: IProjectIssues;
|
||||
issuesFilter: IProjectIssuesFilter;
|
||||
|
|
@ -142,11 +137,6 @@ export const useIssues = <T extends EIssuesStoreType>(storeType?: T): TStoreIssu
|
|||
issues: context.issue.archivedIssues,
|
||||
issuesFilter: context.issue.archivedIssuesFilter,
|
||||
}) as TStoreIssues[T];
|
||||
case EIssuesStoreType.DRAFT:
|
||||
return merge(defaultStore, {
|
||||
issues: context.issue.draftIssues,
|
||||
issuesFilter: context.issue.draftIssuesFilter,
|
||||
}) as TStoreIssues[T];
|
||||
case EIssuesStoreType.EPIC:
|
||||
return merge(defaultStore, {
|
||||
issues: context.issue.projectEpics,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ type DNDStoreType =
|
|||
| EIssuesStoreType.MODULE
|
||||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.DRAFT
|
||||
| EIssuesStoreType.PROFILE
|
||||
| EIssuesStoreType.ARCHIVED
|
||||
| EIssuesStoreType.WORKSPACE_DRAFT
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ export const useIssuesActions = (storeType: EIssuesStoreType): IssueActions => {
|
|||
const projectViewIssueActions = useProjectViewIssueActions();
|
||||
const globalIssueActions = useGlobalIssueActions();
|
||||
const profileIssueActions = useProfileIssueActions();
|
||||
const draftIssueActions = useDraftIssueActions();
|
||||
const archivedIssueActions = useArchivedIssueActions();
|
||||
const workspaceDraftIssueActions = useWorkspaceDraftIssueActions();
|
||||
const teamProjectWorkItemsActions = useTeamProjectWorkItemsActions();
|
||||
|
|
@ -68,8 +67,6 @@ export const useIssuesActions = (storeType: EIssuesStoreType): IssueActions => {
|
|||
return teamIssueActions;
|
||||
case EIssuesStoreType.ARCHIVED:
|
||||
return archivedIssueActions;
|
||||
case EIssuesStoreType.DRAFT:
|
||||
return draftIssueActions;
|
||||
case EIssuesStoreType.CYCLE:
|
||||
return cycleIssueActions;
|
||||
case EIssuesStoreType.MODULE:
|
||||
|
|
@ -644,76 +641,6 @@ const useProjectViewIssueActions = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const useDraftIssueActions = () => {
|
||||
// router
|
||||
const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = useParams();
|
||||
const workspaceSlug = routerWorkspaceSlug?.toString();
|
||||
const projectId = routerProjectId?.toString();
|
||||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
|
||||
|
||||
const fetchIssues = useCallback(
|
||||
async (loadType: TLoader, options: IssuePaginationOptions) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return issues.fetchIssues(workspaceSlug.toString(), projectId.toString(), loadType, options);
|
||||
},
|
||||
[issues.fetchIssues, workspaceSlug, projectId]
|
||||
);
|
||||
const fetchNextIssues = useCallback(
|
||||
async (groupId?: string, subGroupId?: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), groupId, subGroupId);
|
||||
},
|
||||
[issues.fetchIssues, workspaceSlug, projectId]
|
||||
);
|
||||
|
||||
const createIssue = useCallback(
|
||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await issues.createIssue(workspaceSlug, projectId, data);
|
||||
},
|
||||
[issues.createIssue, workspaceSlug]
|
||||
);
|
||||
const updateIssue = useCallback(
|
||||
async (projectId: string | undefined | null, issueId: string, data: Partial<TIssue>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await issues.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||
},
|
||||
[issues.updateIssue, workspaceSlug]
|
||||
);
|
||||
const removeIssue = useCallback(
|
||||
async (projectId: string | undefined | null, issueId: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await issues.removeIssue(workspaceSlug, projectId, issueId);
|
||||
},
|
||||
[issues.removeIssue, workspaceSlug]
|
||||
);
|
||||
|
||||
const updateFilters = useCallback(
|
||||
async (
|
||||
projectId: string,
|
||||
filterType: EIssueFilterType,
|
||||
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
|
||||
) => {
|
||||
if (!workspaceSlug) return;
|
||||
return await issuesFilter.updateFilters(workspaceSlug, projectId, filterType, filters);
|
||||
},
|
||||
[issuesFilter.updateFilters]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
fetchIssues,
|
||||
fetchNextIssues,
|
||||
createIssue,
|
||||
updateIssue,
|
||||
removeIssue,
|
||||
updateFilters,
|
||||
}),
|
||||
[fetchIssues, createIssue, updateIssue, removeIssue, updateFilters]
|
||||
);
|
||||
};
|
||||
|
||||
const useArchivedIssueActions = () => {
|
||||
// router
|
||||
const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = useParams();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export * from "./issue_archive.service";
|
||||
export * from "./issue.service";
|
||||
export * from "./issue_draft.service";
|
||||
export * from "./issue_reaction.service";
|
||||
export * from "./issue_label.service";
|
||||
export * from "./issue_attachment.service";
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { TIssue, TIssuesResponse } from "@plane/types";
|
||||
import { APIService } from "@/services/api.service";
|
||||
// helpers
|
||||
|
||||
export class IssueDraftService extends APIService {
|
||||
constructor() {
|
||||
super(API_BASE_URL);
|
||||
}
|
||||
|
||||
async getDraftIssues(workspaceSlug: string, projectId: string, query?: any, config = {}): Promise<TIssuesResponse> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`,
|
||||
{
|
||||
params: { ...query },
|
||||
},
|
||||
config
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async createDraftIssue(workspaceSlug: string, projectId: string, data: any): Promise<TIssue> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<void> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, data)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteDraftIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<void> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
|
||||
async getDraftIssueById(workspaceSlug: string, projectId: string, issueId: string, queries?: any): Promise<TIssue> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`, {
|
||||
params: queries,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
import isArray from "lodash/isArray";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import pickBy from "lodash/pickBy";
|
||||
import set from "lodash/set";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
// base class
|
||||
import { computedFn } from "mobx-utils";
|
||||
import { EIssueFilterType } from "@plane/constants";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueFilterOptions,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
TIssueKanbanFilters,
|
||||
IIssueFilters,
|
||||
TIssueParams,
|
||||
IssuePaginationOptions,
|
||||
} from "@plane/types";
|
||||
import { handleIssueQueryParamsByLayout } from "@plane/utils";
|
||||
import { IssueFiltersService } from "@/services/issue_filter.service";
|
||||
import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
|
||||
// helpers
|
||||
// types
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
// constants
|
||||
// services
|
||||
|
||||
export interface IDraftIssuesFilter extends IBaseIssueFilterStore {
|
||||
//helper actions
|
||||
getFilterParams: (
|
||||
options: IssuePaginationOptions,
|
||||
projectId: string,
|
||||
cursor: string | undefined,
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined
|
||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||
getIssueFilters(projectId: string): IIssueFilters | undefined;
|
||||
// action
|
||||
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
updateFilters: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
filterType: EIssueFilterType,
|
||||
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftIssuesFilter {
|
||||
// observables
|
||||
filters: { [projectId: string]: IIssueFilters } = {};
|
||||
// root store
|
||||
rootIssueStore: IIssueRootStore;
|
||||
// services
|
||||
issueFilterService;
|
||||
|
||||
constructor(_rootStore: IIssueRootStore) {
|
||||
super();
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
filters: observable,
|
||||
// computed
|
||||
issueFilters: computed,
|
||||
appliedFilters: computed,
|
||||
// actions
|
||||
fetchFilters: action,
|
||||
updateFilters: action,
|
||||
});
|
||||
// root store
|
||||
this.rootIssueStore = _rootStore;
|
||||
// services
|
||||
this.issueFilterService = new IssueFiltersService();
|
||||
}
|
||||
|
||||
get issueFilters() {
|
||||
const projectId = this.rootIssueStore.projectId;
|
||||
if (!projectId) return undefined;
|
||||
|
||||
return this.getIssueFilters(projectId);
|
||||
}
|
||||
|
||||
get appliedFilters() {
|
||||
const projectId = this.rootIssueStore.projectId;
|
||||
if (!projectId) return undefined;
|
||||
|
||||
return this.getAppliedFilters(projectId);
|
||||
}
|
||||
|
||||
getIssueFilters(projectId: string) {
|
||||
const displayFilters = this.filters[projectId] || undefined;
|
||||
if (!projectId || isEmpty(displayFilters)) return undefined;
|
||||
|
||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
|
||||
|
||||
return _filters;
|
||||
}
|
||||
|
||||
getAppliedFilters(projectId: string) {
|
||||
const userFilters = this.getIssueFilters(projectId);
|
||||
if (!userFilters) return undefined;
|
||||
|
||||
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||
if (!filteredParams) return undefined;
|
||||
|
||||
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
||||
userFilters?.filters as IIssueFilterOptions,
|
||||
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
||||
filteredParams
|
||||
);
|
||||
|
||||
return filteredRouteParams;
|
||||
}
|
||||
|
||||
getFilterParams = computedFn(
|
||||
(
|
||||
options: IssuePaginationOptions,
|
||||
projectId: string,
|
||||
cursor: string | undefined,
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined
|
||||
) => {
|
||||
const filterParams = this.getAppliedFilters(projectId);
|
||||
|
||||
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||
return paginationParams;
|
||||
}
|
||||
);
|
||||
|
||||
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
||||
const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.DRAFT, workspaceSlug, projectId, undefined);
|
||||
|
||||
const filters: IIssueFilterOptions = this.computedFilters(_filters?.filters);
|
||||
const displayFilters: IIssueDisplayFilterOptions = this.computedDisplayFilters(_filters?.display_filters);
|
||||
const displayProperties: IIssueDisplayProperties = this.computedDisplayProperties(_filters?.display_properties);
|
||||
const kanbanFilters = {
|
||||
group_by: [],
|
||||
sub_group_by: [],
|
||||
};
|
||||
kanbanFilters.group_by = _filters?.kanban_filters?.group_by || [];
|
||||
kanbanFilters.sub_group_by = _filters?.kanban_filters?.sub_group_by || [];
|
||||
|
||||
runInAction(() => {
|
||||
set(this.filters, [projectId, "filters"], filters);
|
||||
set(this.filters, [projectId, "displayFilters"], displayFilters);
|
||||
set(this.filters, [projectId, "displayProperties"], displayProperties);
|
||||
set(this.filters, [projectId, "kanbanFilters"], kanbanFilters);
|
||||
});
|
||||
};
|
||||
|
||||
updateFilters = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
type: EIssueFilterType,
|
||||
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
|
||||
) => {
|
||||
try {
|
||||
if (isEmpty(this.filters) || isEmpty(this.filters[projectId]) || isEmpty(filters)) return;
|
||||
|
||||
const _filters = {
|
||||
filters: this.filters[projectId].filters as IIssueFilterOptions,
|
||||
displayFilters: this.filters[projectId].displayFilters as IIssueDisplayFilterOptions,
|
||||
displayProperties: this.filters[projectId].displayProperties as IIssueDisplayProperties,
|
||||
kanbanFilters: this.filters[projectId].kanbanFilters as TIssueKanbanFilters,
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case EIssueFilterType.FILTERS: {
|
||||
const updatedFilters = filters as IIssueFilterOptions;
|
||||
_filters.filters = { ..._filters.filters, ...updatedFilters };
|
||||
|
||||
runInAction(() => {
|
||||
Object.keys(updatedFilters).forEach((_key) => {
|
||||
set(this.filters, [projectId, "filters", _key], updatedFilters[_key as keyof IIssueFilterOptions]);
|
||||
});
|
||||
});
|
||||
const appliedFilters = _filters.filters || {};
|
||||
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
|
||||
this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
isEmpty(filteredFilters) ? "init-loader" : "mutation"
|
||||
);
|
||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
|
||||
filters: _filters.filters,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case EIssueFilterType.DISPLAY_FILTERS: {
|
||||
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
|
||||
_filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters };
|
||||
|
||||
// set sub_group_by to null if group_by is set to null
|
||||
if (_filters.displayFilters.group_by === null) {
|
||||
_filters.displayFilters.sub_group_by = null;
|
||||
updatedDisplayFilters.sub_group_by = null;
|
||||
}
|
||||
// set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same
|
||||
if (
|
||||
_filters.displayFilters.layout === "kanban" &&
|
||||
_filters.displayFilters.group_by === _filters.displayFilters.sub_group_by
|
||||
) {
|
||||
_filters.displayFilters.sub_group_by = null;
|
||||
updatedDisplayFilters.sub_group_by = null;
|
||||
}
|
||||
// set group_by to state if layout is switched to kanban and group_by is null
|
||||
if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) {
|
||||
_filters.displayFilters.group_by = "state";
|
||||
updatedDisplayFilters.group_by = "state";
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
Object.keys(updatedDisplayFilters).forEach((_key) => {
|
||||
set(
|
||||
this.filters,
|
||||
[projectId, "displayFilters", _key],
|
||||
updatedDisplayFilters[_key as keyof IIssueDisplayFilterOptions]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
|
||||
this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
|
||||
}
|
||||
|
||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
|
||||
display_filters: _filters.displayFilters,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case EIssueFilterType.DISPLAY_PROPERTIES: {
|
||||
const updatedDisplayProperties = filters as IIssueDisplayProperties;
|
||||
_filters.displayProperties = { ..._filters.displayProperties, ...updatedDisplayProperties };
|
||||
|
||||
runInAction(() => {
|
||||
Object.keys(updatedDisplayProperties).forEach((_key) => {
|
||||
set(
|
||||
this.filters,
|
||||
[projectId, "displayProperties", _key],
|
||||
updatedDisplayProperties[_key as keyof IIssueDisplayProperties]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
|
||||
display_properties: _filters.displayProperties,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case EIssueFilterType.KANBAN_FILTERS: {
|
||||
const updatedKanbanFilters = filters as TIssueKanbanFilters;
|
||||
_filters.kanbanFilters = { ..._filters.kanbanFilters, ...updatedKanbanFilters };
|
||||
|
||||
const currentUserId = this.rootIssueStore.currentUserId;
|
||||
if (currentUserId)
|
||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
|
||||
kanban_filters: _filters.kanbanFilters,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
Object.keys(updatedKanbanFilters).forEach((_key) => {
|
||||
set(
|
||||
this.filters,
|
||||
[projectId, "kanbanFilters", _key],
|
||||
updatedKanbanFilters[_key as keyof TIssueKanbanFilters]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this.fetchFilters(workspaceSlug, projectId);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./filter.store";
|
||||
export * from "./issue.store";
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
import { action, makeObservable, runInAction } from "mobx";
|
||||
// base class
|
||||
// services
|
||||
// types
|
||||
import {
|
||||
TIssue,
|
||||
TLoader,
|
||||
ViewFlags,
|
||||
IssuePaginationOptions,
|
||||
TIssuesResponse,
|
||||
TBulkOperationsPayload,
|
||||
} from "@plane/types";
|
||||
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { IDraftIssuesFilter } from "./filter.store";
|
||||
|
||||
export interface IDraftIssues extends IBaseIssuesStore {
|
||||
// observable
|
||||
|
||||
viewFlags: ViewFlags;
|
||||
// actions
|
||||
fetchIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader,
|
||||
option: IssuePaginationOptions
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
fetchIssuesWithExistingPagination: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
|
||||
fetchNextIssues: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) => Promise<TIssuesResponse | undefined>;
|
||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
|
||||
bulkUpdateProperties: (workspaceSlug: string, projectId: string, data: TBulkOperationsPayload) => Promise<void>;
|
||||
|
||||
archiveBulkIssues: undefined;
|
||||
quickAddIssue: undefined;
|
||||
archiveIssue: undefined;
|
||||
}
|
||||
|
||||
export class DraftIssues extends BaseIssuesStore implements IDraftIssues {
|
||||
viewFlags = {
|
||||
enableQuickAdd: false,
|
||||
enableIssueCreation: true,
|
||||
enableInlineEditing: true,
|
||||
};
|
||||
// filter store
|
||||
issueFilterStore: IDraftIssuesFilter;
|
||||
|
||||
constructor(_rootStore: IIssueRootStore, issueFilterStore: IDraftIssuesFilter) {
|
||||
super(_rootStore, issueFilterStore);
|
||||
makeObservable(this, {
|
||||
// action
|
||||
fetchIssues: action,
|
||||
fetchNextIssues: action,
|
||||
fetchIssuesWithExistingPagination: action,
|
||||
});
|
||||
// filter store
|
||||
this.issueFilterStore = issueFilterStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the project details
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
*/
|
||||
fetchParentStats = async (workspaceSlug: string, projectId?: string) => {
|
||||
projectId && this.rootIssueStore.rootStore.projectRoot.project.fetchProjectDetails(workspaceSlug, projectId);
|
||||
};
|
||||
|
||||
/** */
|
||||
updateParentStats = () => {};
|
||||
|
||||
/**
|
||||
* This method is called to fetch the first issues of pagination
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param loadType
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
fetchIssues = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader = "init-loader",
|
||||
options: IssuePaginationOptions,
|
||||
isExistingPaginationOptions: boolean = false
|
||||
) => {
|
||||
try {
|
||||
// set loader and clear store
|
||||
runInAction(() => {
|
||||
this.setLoader(loadType);
|
||||
});
|
||||
this.clear(!isExistingPaginationOptions);
|
||||
|
||||
// get params from pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(options, projectId, undefined, undefined, undefined);
|
||||
// call the fetch issues API with the params
|
||||
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params, {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
|
||||
// after fetching issues, call the base method to process the response further
|
||||
this.onfetchIssues(response, options, workspaceSlug, projectId, undefined, !isExistingPaginationOptions);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set loader to undefined if errored out
|
||||
this.setLoader(undefined);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called subsequent pages of pagination
|
||||
* if groupId/subgroupId is provided, only that specific group's next page is fetched
|
||||
* else all the groups' next page is fetched
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param groupId
|
||||
* @param subGroupId
|
||||
* @returns
|
||||
*/
|
||||
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
|
||||
const cursorObject = this.getPaginationData(groupId, subGroupId);
|
||||
// if there are no pagination options and the next page results do not exist the return
|
||||
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||
try {
|
||||
// set Loader
|
||||
this.setLoader("pagination", groupId, subGroupId);
|
||||
|
||||
// get params from stored pagination options
|
||||
const params = this.issueFilterStore?.getFilterParams(
|
||||
this.paginationOptions,
|
||||
projectId,
|
||||
this.getNextCursor(groupId, subGroupId),
|
||||
groupId,
|
||||
subGroupId
|
||||
);
|
||||
// call the fetch issues API with the params for next page in issues
|
||||
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params);
|
||||
|
||||
// after the next page of issues are fetched, call the base method to process the response
|
||||
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
// set Loader as undefined if errored out
|
||||
this.setLoader(undefined, groupId, subGroupId);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Method exists to fetch the first page of the issues with the existing stored pagination
|
||||
* This is useful for refetching when filters, groupBy, orderBy etc changes
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param loadType
|
||||
* @returns
|
||||
*/
|
||||
fetchIssuesWithExistingPagination = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
loadType: TLoader = "mutation"
|
||||
) => {
|
||||
if (!this.paginationOptions) return;
|
||||
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, true);
|
||||
};
|
||||
|
||||
// Using aliased names as they cannot be overridden in other stores
|
||||
createIssue = this.createDraftIssue;
|
||||
updateIssue = this.updateDraftIssue;
|
||||
|
||||
// Setting them as undefined as they can not performed on draft issues
|
||||
archiveBulkIssues = undefined;
|
||||
quickAddIssue = undefined;
|
||||
archiveIssue = undefined;
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ import { updatePersistentLayer } from "@/local-db/utils/utils";
|
|||
import { workItemSortWithOrderByExtended } from "@/plane-web/store/issue/helpers/base-issue.store";
|
||||
// services
|
||||
import { CycleService } from "@/services/cycle.service";
|
||||
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
|
||||
import { IssueArchiveService, IssueService } from "@/services/issue";
|
||||
import { ModuleService } from "@/services/module.service";
|
||||
//
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
|
|
@ -194,7 +194,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
|||
// services
|
||||
issueService;
|
||||
issueArchiveService;
|
||||
issueDraftService;
|
||||
moduleService;
|
||||
cycleService;
|
||||
// root store
|
||||
|
|
@ -238,8 +237,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
|||
|
||||
createIssue: action,
|
||||
issueUpdate: action,
|
||||
createDraftIssue: action,
|
||||
updateDraftIssue: action,
|
||||
updateIssueDates: action,
|
||||
issueQuickAdd: action.bound,
|
||||
removeIssue: action.bound,
|
||||
|
|
@ -264,7 +261,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
|||
|
||||
this.issueService = new IssueService(serviceType);
|
||||
this.issueArchiveService = new IssueArchiveService();
|
||||
this.issueDraftService = new IssueDraftService();
|
||||
this.moduleService = new ModuleService();
|
||||
this.cycleService = new CycleService();
|
||||
|
||||
|
|
@ -613,54 +609,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to Create Issue but for creating Draft issues
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param data draft issue data
|
||||
* @returns
|
||||
*/
|
||||
async createDraftIssue(workspaceSlug: string, projectId: string, data: Partial<TIssue>) {
|
||||
// call API to create a Draft issue
|
||||
const response = await this.issueDraftService.createDraftIssue(workspaceSlug, projectId, data);
|
||||
// call Fetch parent stats
|
||||
this.fetchParentStats(workspaceSlug, projectId);
|
||||
// Add issue to store
|
||||
this.addIssue(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to update issue but for draft issues.
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param issueId
|
||||
* @param data Partial Issue Data to be updated
|
||||
*/
|
||||
async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) {
|
||||
// Store Before state of the issue
|
||||
const issueBeforeUpdate = clone(this.rootIssueStore.issues.getIssueById(issueId));
|
||||
try {
|
||||
// Update the Respective Stores
|
||||
this.rootIssueStore.issues.updateIssue(issueId, data);
|
||||
this.updateIssueList({ ...issueBeforeUpdate, ...data } as TIssue, issueBeforeUpdate);
|
||||
|
||||
// call API to update the issue
|
||||
await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data);
|
||||
|
||||
// call Fetch parent stats
|
||||
this.fetchParentStats(workspaceSlug, projectId);
|
||||
|
||||
// If the issue is updated to not a draft issue anymore remove from the store list
|
||||
if (!isNil(data.is_draft) && !data.is_draft) this.removeIssueFromList(issueId);
|
||||
} catch (error) {
|
||||
// If errored out update store again to revert the change
|
||||
this.rootIssueStore.issues.updateIssue(issueId, issueBeforeUpdate ?? {});
|
||||
this.updateIssueList(issueBeforeUpdate, { ...issueBeforeUpdate, ...data } as TIssue);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to delete an issue
|
||||
* @param workspaceSlug
|
||||
|
|
|
|||
|
|
@ -5,18 +5,13 @@ import { EIssueServiceType, TIssue, TIssueServiceType } from "@plane/types";
|
|||
// local
|
||||
import { persistence } from "@/local-db/storage.sqlite";
|
||||
// services
|
||||
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
|
||||
import { IssueArchiveService, WorkspaceDraftService, IssueService } from "@/services/issue";
|
||||
// types
|
||||
import { IIssueDetail } from "./root.store";
|
||||
|
||||
export interface IIssueStoreActions {
|
||||
// actions
|
||||
fetchIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
issueStatus?: "DEFAULT" | "DRAFT"
|
||||
) => Promise<TIssue>;
|
||||
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
|
|
@ -52,7 +47,7 @@ export class IssueStore implements IIssueStore {
|
|||
issueService;
|
||||
epicService;
|
||||
issueArchiveService;
|
||||
issueDraftService;
|
||||
draftWorkItemService;
|
||||
|
||||
constructor(rootStore: IIssueDetail, serviceType: TIssueServiceType) {
|
||||
makeObservable(this, {
|
||||
|
|
@ -66,7 +61,7 @@ export class IssueStore implements IIssueStore {
|
|||
this.issueService = new IssueService(serviceType);
|
||||
this.epicService = new IssueService(EIssueServiceType.EPICS);
|
||||
this.issueArchiveService = new IssueArchiveService(serviceType);
|
||||
this.issueDraftService = new IssueDraftService();
|
||||
this.draftWorkItemService = new WorkspaceDraftService();
|
||||
}
|
||||
|
||||
getIsFetchingIssueDetails = computedFn((issueId: string | undefined) => {
|
||||
|
|
@ -93,7 +88,7 @@ export class IssueStore implements IIssueStore {
|
|||
});
|
||||
|
||||
// actions
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, issueStatus = "DEFAULT") => {
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
const query = {
|
||||
expand: "issue_reactions,issue_attachments,issue_link,parent",
|
||||
};
|
||||
|
|
@ -112,9 +107,7 @@ export class IssueStore implements IIssueStore {
|
|||
this.localDBIssueDescription = issueId;
|
||||
}
|
||||
|
||||
if (issueStatus === "DRAFT")
|
||||
issue = await this.issueDraftService.getDraftIssueById(workspaceSlug, projectId, issueId, query);
|
||||
else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
|
||||
await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
|
||||
|
||||
if (!issue) throw new Error("Work item not found");
|
||||
|
||||
|
|
|
|||
|
|
@ -272,12 +272,8 @@ export abstract class IssueDetail implements IIssueDetail {
|
|||
setIssueLinkData = (issueLinkData: TIssueLink | null) => (this.issueLinkData = issueLinkData);
|
||||
|
||||
// issue
|
||||
fetchIssue = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
issueStatus: "DEFAULT" | "DRAFT" = "DEFAULT"
|
||||
) => this.issue.fetchIssue(workspaceSlug, projectId, issueId, issueStatus);
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||
this.issue.fetchIssue(workspaceSlug, projectId, issueId);
|
||||
fetchIssueWithIdentifier = async (workspaceSlug: string, projectIdentifier: string, sequenceId: string) =>
|
||||
this.issue.fetchIssueWithIdentifier(workspaceSlug, projectIdentifier, sequenceId);
|
||||
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) =>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import { IWorkspaceMembership } from "@/store/member/workspace-member.store";
|
|||
// issues data store
|
||||
import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived";
|
||||
import { ICycleIssuesFilter, CycleIssuesFilter, ICycleIssues, CycleIssues } from "./cycle";
|
||||
import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft";
|
||||
import { IIssueStore, IssueStore } from "./issue.store";
|
||||
import { ICalendarStore, CalendarStore } from "./issue_calendar_view.store";
|
||||
import { IIssueKanBanViewStore, IssueKanBanViewStore } from "./issue_kanban_view.store";
|
||||
|
|
@ -115,9 +114,6 @@ export interface IIssueRootStore {
|
|||
archivedIssuesFilter: IArchivedIssuesFilter;
|
||||
archivedIssues: IArchivedIssues;
|
||||
|
||||
draftIssuesFilter: IDraftIssuesFilter;
|
||||
draftIssues: IDraftIssues;
|
||||
|
||||
issueKanBanView: IIssueKanBanViewStore;
|
||||
issueCalendarView: ICalendarStore;
|
||||
|
||||
|
|
@ -186,9 +182,6 @@ export class IssueRootStore implements IIssueRootStore {
|
|||
archivedIssuesFilter: IArchivedIssuesFilter;
|
||||
archivedIssues: IArchivedIssues;
|
||||
|
||||
draftIssuesFilter: IDraftIssuesFilter;
|
||||
draftIssues: IDraftIssues;
|
||||
|
||||
issueKanBanView: IIssueKanBanViewStore;
|
||||
issueCalendarView: ICalendarStore;
|
||||
|
||||
|
|
@ -280,9 +273,6 @@ export class IssueRootStore implements IIssueRootStore {
|
|||
this.archivedIssuesFilter = new ArchivedIssuesFilter(this);
|
||||
this.archivedIssues = new ArchivedIssues(this, this.archivedIssuesFilter);
|
||||
|
||||
this.draftIssuesFilter = new DraftIssuesFilter(this);
|
||||
this.draftIssues = new DraftIssues(this, this.draftIssuesFilter);
|
||||
|
||||
this.issueKanBanView = new IssueKanBanViewStore(this);
|
||||
this.issueCalendarView = new CalendarStore();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue