import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { mutate } from "swr"; import { useForm } from "react-hook-form"; // headless ui import { Disclosure, Popover, Transition } from "@headlessui/react"; // services import { CycleService } from "services/cycle.service"; // hooks import useToast from "hooks/use-toast"; // components import { SidebarProgressStats } from "components/core"; import ProgressChart from "components/core/sidebar/progress-chart"; import { CycleDeleteModal } from "components/cycles/cycle-delete-modal"; // ui import { CustomMenu, CustomRangeDatePicker } from "components/ui"; import { Loader, ProgressBar } from "@plane/ui"; // icons import { CalendarDaysIcon, ChartPieIcon, ArrowLongRightIcon, TrashIcon, UserCircleIcon, ChevronDownIcon, DocumentIcon, LinkIcon, } from "@heroicons/react/24/outline"; import { ExclamationIcon } from "components/icons"; // helpers import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper"; import { isDateGreaterThanToday, renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types import { IUser, ICycle } from "types"; // fetch-keys import { CYCLE_DETAILS } from "constants/fetch-keys"; type Props = { cycle: ICycle | undefined; isOpen: boolean; cycleStatus: string; isCompleted: boolean; user: IUser | undefined; }; const cycleService = new CycleService(); export const CycleDetailsSidebar: React.FC = ({ cycle, isOpen, cycleStatus, isCompleted, user }) => { const [cycleDeleteModal, setCycleDeleteModal] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, cycleId } = router.query as { workspaceSlug: string; projectId: string; cycleId: string; }; const { setToastAlert } = useToast(); const defaultValues: Partial = { start_date: new Date().toString(), end_date: new Date().toString(), }; const { setValue, reset, watch } = useForm({ defaultValues, }); const submitChanges = (data: Partial) => { if (!workspaceSlug || !projectId || !cycleId) return; mutate(CYCLE_DETAILS(cycleId as string), (prevData) => ({ ...(prevData as ICycle), ...data }), false); cycleService .patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data, user) .then(() => mutate(CYCLE_DETAILS(cycleId as string))) .catch((e) => console.log(e)); }; const handleCopyText = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`) .then(() => { setToastAlert({ type: "success", title: "Cycle link copied to clipboard", }); }) .catch(() => { setToastAlert({ type: "error", title: "Some error occurred", }); }); }; useEffect(() => { if (cycle) reset({ ...cycle, }); }, [cycle, reset]); const dateChecker = async (payload: any) => { try { const res = await cycleService.cycleDateCheck(workspaceSlug as string, projectId as string, payload); return res.status; } catch (err) { return false; } }; const handleStartDateChange = async (date: string) => { setValue("start_date", date); if ( watch("start_date") && watch("end_date") && watch("start_date") !== "" && watch("end_date") && watch("start_date") !== "" ) { if (!isDateGreaterThanToday(`${watch("end_date")}`)) { setToastAlert({ type: "error", title: "Error!", message: "Unable to create cycle in past date. Please enter a valid date.", }); return; } if (cycle?.start_date && cycle?.end_date) { const isDateValidForExistingCycle = await dateChecker({ start_date: `${watch("start_date")}`, end_date: `${watch("end_date")}`, cycle_id: cycle.id, }); if (isDateValidForExistingCycle) { await submitChanges({ start_date: renderDateFormat(`${watch("start_date")}`), end_date: renderDateFormat(`${watch("end_date")}`), }); setToastAlert({ type: "success", title: "Success!", message: "Cycle updated successfully.", }); return; } else { setToastAlert({ type: "error", title: "Error!", message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); return; } } const isDateValid = await dateChecker({ start_date: `${watch("start_date")}`, end_date: `${watch("end_date")}`, }); if (isDateValid) { submitChanges({ start_date: renderDateFormat(`${watch("start_date")}`), end_date: renderDateFormat(`${watch("end_date")}`), }); setToastAlert({ type: "success", title: "Success!", message: "Cycle updated successfully.", }); } else { setToastAlert({ type: "error", title: "Error!", message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); } } }; const handleEndDateChange = async (date: string) => { setValue("end_date", date); if ( watch("start_date") && watch("end_date") && watch("start_date") !== "" && watch("end_date") && watch("start_date") !== "" ) { if (!isDateGreaterThanToday(`${watch("end_date")}`)) { setToastAlert({ type: "error", title: "Error!", message: "Unable to create cycle in past date. Please enter a valid date.", }); return; } if (cycle?.start_date && cycle?.end_date) { const isDateValidForExistingCycle = await dateChecker({ start_date: `${watch("start_date")}`, end_date: `${watch("end_date")}`, cycle_id: cycle.id, }); if (isDateValidForExistingCycle) { await submitChanges({ start_date: renderDateFormat(`${watch("start_date")}`), end_date: renderDateFormat(`${watch("end_date")}`), }); setToastAlert({ type: "success", title: "Success!", message: "Cycle updated successfully.", }); return; } else { setToastAlert({ type: "error", title: "Error!", message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); return; } } const isDateValid = await dateChecker({ start_date: `${watch("start_date")}`, end_date: `${watch("end_date")}`, }); if (isDateValid) { submitChanges({ start_date: renderDateFormat(`${watch("start_date")}`), end_date: renderDateFormat(`${watch("end_date")}`), }); setToastAlert({ type: "success", title: "Success!", message: "Cycle updated successfully.", }); } else { setToastAlert({ type: "error", title: "Error!", message: "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", }); } } }; const isStartValid = new Date(`${cycle?.start_date}`) <= new Date(); const isEndValid = new Date(`${cycle?.end_date}`) >= new Date(`${cycle?.start_date}`); const progressPercentage = cycle ? Math.round((cycle.completed_issues / cycle.total_issues) * 100) : null; return ( <> {cycle && ( setCycleDeleteModal(false)} onSubmit={() => {}} workspaceSlug={workspaceSlug} projectId={projectId} /> )}
{cycle ? ( <>
{capitalizeFirstLetter(cycleStatus)}
{({}) => ( <> {renderShortDateWithYearFormat( new Date(`${watch("start_date") ? watch("start_date") : cycle?.start_date}`), "Start date" )} { if (val) { handleStartDateChange(val); } }} startDate={watch("start_date") ? `${watch("start_date")}` : null} endDate={watch("end_date") ? `${watch("end_date")}` : null} maxDate={new Date(`${watch("end_date")}`)} selectsStart /> )} {({}) => ( <> {renderShortDateWithYearFormat( new Date(`${watch("end_date") ? watch("end_date") : cycle?.end_date}`), "End date" )} { if (val) { handleEndDateChange(val); } }} startDate={watch("start_date") ? `${watch("start_date")}` : null} endDate={watch("end_date") ? `${watch("end_date")}` : null} minDate={new Date(`${watch("start_date")}`)} selectsEnd /> )}

{cycle.name}

{!isCompleted && ( setCycleDeleteModal(true)}> Delete )} Copy link
{cycle.description}
Lead
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? ( {cycle.owned_by.display_name} ) : ( {cycle.owned_by.display_name.charAt(0)} )} {cycle.owned_by.display_name}
Progress
{cycle.completed_issues}/{cycle.total_issues}
{({ open }) => (
Progress {!open && progressPercentage ? ( {progressPercentage ? `${progressPercentage}%` : ""} ) : ( "" )}
{isStartValid && isEndValid ? ( ) : (
{cycleStatus === "upcoming" ? "Cycle is yet to start." : "Invalid date. Please enter valid date."}
)}
{isStartValid && isEndValid ? (
Pending Issues -{" "} {cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)}
Ideal
Current
) : ( "" )}
)}
{({ open }) => (
Other Information
{cycle.total_issues > 0 ? ( ) : (
No issues found. Please add issue.
)}
{cycle.total_issues > 0 ? (
) : ( "" )}
)}
) : (
)}
); };