[WEB-2941]chore: added transfer issues button to cycles (#6296)

* chore: removed refacotr changes:w

* chore: removed requirements modifications
This commit is contained in:
Vamsi Krishna 2025-01-02 12:33:45 +05:30 committed by GitHub
parent df671d13c7
commit a555df27e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 14 deletions

View file

@ -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 { CycleQuickActions, TransferIssuesModal } from "@/components/cycles";
import { DateRangeDropdown } from "@/components/dropdowns";
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
// constants
@ -32,6 +33,7 @@ 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
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// services
@ -55,6 +57,8 @@ const defaultValues: Partial<ICycle> = {
export const CycleListItemAction: FC<Props> = observer((props) => {
const { workspaceSlug, projectId, cycleId, cycleDetails, parentRef, isActive = false } = props;
//states
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
// hooks
const { isMobile } = usePlatformOS();
// router
@ -76,6 +80,8 @@ export const CycleListItemAction: FC<Props> = 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 +224,11 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
return (
<>
<TransferIssuesModal
handleClose={() => setTransferIssuesModal(false)}
isOpen={transferIssuesModal}
cycleId={cycleId.toString()}
/>
<button
onClick={openCycleOverview}
className={`z-[1] flex text-custom-primary-200 text-xs gap-1 flex-shrink-0 ${isMobile || (isActive && !searchParams.has("peekCycle")) ? "flex" : "hidden group-hover:flex"}`}
@ -233,6 +244,18 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
</div>
)}
{showTransferIssues && (
<div
className="px-2 h-6 text-custom-primary-200 flex items-center gap-1 cursor-pointer"
onClick={() => {
setTransferIssuesModal(true);
}}
>
<TransferIcon className="fill-custom-primary-200 w-4" />
<span>Transfer {transferableIssuesCount} issues</span>
</div>
)}
{!isActive && (
<Controller
control={control}

View file

@ -17,42 +17,58 @@ import { useCycle, useIssues } from "@/hooks/store";
type Props = {
isOpen: boolean;
handleClose: () => void;
cycleId: string;
};
export const TransferIssuesModal: React.FC<Props> = 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<Props> = observer((props) => {
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 py-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between px-5">
<div className="flex items-center gap-3">
<TransferIcon className="h-4 w-4" color="#495057" />
<div className="flex items-center gap-1">
<TransferIcon className="w-5 fill-custom-text-100" />
<h4 className="text-xl font-medium text-custom-text-100">Transfer Issues</h4>
</div>
<button onClick={handleClose}>
@ -107,7 +123,7 @@ export const TransferIssuesModal: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-2 border-b border-custom-border-200 px-5 pb-3">
<Search className="h-4 w-4 text-custom-text-200" />
<input
className="bg-custom-background-90 outline-none"
className="outline-none text-sm"
placeholder="Search for a cycle..."
onChange={(e) => setQuery(e.target.value)}
value={query}

View file

@ -86,7 +86,11 @@ export const CycleLayoutRoot: React.FC = observer(() => {
return (
<IssuesStoreContext.Provider value={EIssuesStoreType.CYCLE}>
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
<TransferIssuesModal
handleClose={() => setTransferIssuesModal(false)}
cycleId={cycleId.toString()}
isOpen={transferIssuesModal}
/>
<div className="relative flex h-full w-full flex-col overflow-hidden">
{cycleStatus === "completed" && (
<TransferIssues