diff --git a/packages/ui/src/icons/transfer-icon.tsx b/packages/ui/src/icons/transfer-icon.tsx index 9a5286f94d5..f762f9611e0 100644 --- a/packages/ui/src/icons/transfer-icon.tsx +++ b/packages/ui/src/icons/transfer-icon.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { ISvgIcons } from "./type"; export const TransferIcon: React.FC = ({ className = "fill-current", ...rest }) => ( - + ); diff --git a/web/ce/components/cycles/index.ts b/web/ce/components/cycles/index.ts index 89934687567..48f9a80aa6f 100644 --- a/web/ce/components/cycles/index.ts +++ b/web/ce/components/cycles/index.ts @@ -1,2 +1,3 @@ export * from "./active-cycle"; export * from "./analytics-sidebar"; +export * from "./quick-actions"; diff --git a/web/core/components/cycles/quick-actions.tsx b/web/ce/components/cycles/quick-actions.tsx similarity index 100% rename from web/core/components/cycles/quick-actions.tsx rename to web/ce/components/cycles/quick-actions.tsx diff --git a/web/core/components/cycles/index.ts b/web/core/components/cycles/index.ts index 7013beeabac..4c436509576 100644 --- a/web/core/components/cycles/index.ts +++ b/web/core/components/cycles/index.ts @@ -8,7 +8,6 @@ export * from "./cycles-view"; export * from "./delete-modal"; export * from "./form"; export * from "./modal"; -export * from "./quick-actions"; export * from "./transfer-issues-modal"; export * from "./transfer-issues"; export * from "./cycles-view-header"; diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index d83256b89bc..e5ade685204 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { FC, MouseEvent, useEffect, useMemo } from "react"; +import React, { FC, MouseEvent, useEffect, useMemo, useState } from "react"; import { observer } from "mobx-react"; import { usePathname, useSearchParams } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; @@ -15,11 +15,12 @@ import { LayersIcon, TOAST_TYPE, Tooltip, + TransferIcon, setPromiseToast, setToast, } from "@plane/ui"; // components -import { CycleQuickActions } from "@/components/cycles"; +import { TransferIssuesModal } from "@/components/cycles"; import { DateRangeDropdown } from "@/components/dropdowns"; import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; // constants @@ -32,6 +33,8 @@ import { generateQueryParams } from "@/helpers/router.helper"; import { useCycle, useEventTracker, useMember, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; +// plane web +import { CycleQuickActions } from "@/plane-web/components/cycles"; // plane web constants import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // services @@ -55,6 +58,8 @@ const defaultValues: Partial = { export const CycleListItemAction: FC = observer((props) => { const { workspaceSlug, projectId, cycleId, cycleDetails, parentRef, isActive = false } = props; + //states + const [transferIssuesModal, setTransferIssuesModal] = useState(false); // hooks const { isMobile } = usePlatformOS(); // router @@ -76,6 +81,8 @@ export const CycleListItemAction: FC = observer((props) => { // derived values const cycleStatus = cycleDetails.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft"; const showIssueCount = useMemo(() => cycleStatus === "draft" || cycleStatus === "upcoming", [cycleStatus]); + const transferableIssuesCount = cycleDetails ? cycleDetails.total_issues - cycleDetails.completed_issues : 0; + const showTransferIssues = transferableIssuesCount > 0 && cycleStatus === "completed"; const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT, @@ -218,6 +225,11 @@ export const CycleListItemAction: FC = observer((props) => { return ( <> + setTransferIssuesModal(false)} + isOpen={transferIssuesModal} + cycleId={cycleId.toString()} + />
{ + setTransferIssuesModal(true); + }} + > + + Transfer {transferableIssuesCount} issues +
+ )} + {!isActive && ( void; + cycleId: string; }; export const TransferIssuesModal: React.FC = observer((props) => { - const { isOpen, handleClose } = props; + const { isOpen, handleClose, cycleId } = props; // states const [query, setQuery] = useState(""); // store hooks - const { currentProjectIncompleteCycleIds, getCycleById } = useCycle(); + const { currentProjectIncompleteCycleIds, getCycleById, fetchCycleDetails } = useCycle(); const { issues: { transferIssuesFromCycle }, } = useIssues(EIssuesStoreType.CYCLE); - const { workspaceSlug, projectId, cycleId } = useParams(); + const { workspaceSlug, projectId } = useParams(); - const transferIssue = async (payload: any) => { + const transferIssue = async (payload: { new_cycle_id: string }) => { if (!workspaceSlug || !projectId || !cycleId) return; - // TODO: import transferIssuesFromCycle from store await transferIssuesFromCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), payload) - .then(() => { + .then(async () => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Issues have been transferred successfully", }); + await getCycleDetails(payload.new_cycle_id); }) .catch(() => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", - message: "Issues cannot be transfer. Please try again.", + message: "Unable to transfer Issues. Please try again.", }); }); }; + /**To update issue counts in target cycle and current cycle */ + const getCycleDetails = async (newCycleId: string) => { + const cyclesFetch = [ + fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId), + fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), newCycleId), + ]; + await Promise.all(cyclesFetch).catch((error) => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error", + message: error.error || "Unable to fetch cycle details", + }); + }); + }; + const filteredOptions = currentProjectIncompleteCycleIds?.filter((optionId) => { const cycleDetails = getCycleById(optionId); @@ -96,8 +112,8 @@ export const TransferIssuesModal: React.FC = observer((props) => {
-
- +
+

Transfer Issues