import React from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // services import { CycleService } from "services/cycle.service"; // hooks import useToast from "hooks/use-toast"; // ui import { AssigneesList } from "components/ui/avatar"; import { SingleProgressStats } from "components/core"; import { Loader, Tooltip, LinearProgressIndicator } from "@plane/ui"; // components import ProgressChart from "components/core/sidebar/progress-chart"; import { ActiveCycleProgressStats } from "components/cycles"; // icons import { CalendarDaysIcon } from "@heroicons/react/20/solid"; import { PriorityIcon } from "components/icons/priority-icon"; import { TargetIcon, ContrastIcon, PersonRunningIcon, ArrowRightIcon, TriangleExclamationIcon, AlarmClockIcon, LayerDiagonalIcon, StateGroupIcon, } from "components/icons"; import { StarIcon } from "@heroicons/react/24/outline"; // components import { ViewIssueLabel } from "components/issues"; // helpers import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle, IIssue } from "types"; // fetch-keys import { CURRENT_CYCLE_LIST, CYCLES_LIST, CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; const stateGroups = [ { key: "backlog_issues", title: "Backlog", color: "#dee2e6", }, { key: "unstarted_issues", title: "Unstarted", color: "#26b5ce", }, { key: "started_issues", title: "Started", color: "#f7ae59", }, { key: "cancelled_issues", title: "Cancelled", color: "#d687ff", }, { key: "completed_issues", title: "Completed", color: "#09a953", }, ]; // services const cycleService = new CycleService(); export const ActiveCycleDetails: React.FC = () => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; const { setToastAlert } = useToast(); const { data: currentCycle } = useSWR( workspaceSlug && projectId ? CURRENT_CYCLE_LIST(projectId as string) : null, workspaceSlug && projectId ? () => cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, "current") : null ); const cycle = currentCycle ? currentCycle[0] : null; const { data: issues } = useSWR( workspaceSlug && projectId && cycle?.id ? CYCLE_ISSUES_WITH_PARAMS(cycle?.id, { priority: "urgent,high" }) : null, workspaceSlug && projectId && cycle?.id ? () => cycleService.getCycleIssuesWithParams(workspaceSlug as string, projectId as string, cycle.id, { priority: "urgent,high", }) : null ) as { data: IIssue[] | undefined }; if (!currentCycle) return ( ); if (!cycle) return (

No active cycle

); const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); const groupedIssues: any = { backlog: cycle.backlog_issues, unstarted: cycle.unstarted_issues, started: cycle.started_issues, completed: cycle.completed_issues, cancelled: cycle.cancelled_issues, }; const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); const handleAddToFavorites = () => { if (!workspaceSlug || !projectId || !cycle) return; mutate( CURRENT_CYCLE_LIST(projectId as string), (prevData) => (prevData ?? []).map((c) => ({ ...c, is_favorite: c.id === cycle.id ? true : c.is_favorite, })), false ); mutate( CYCLES_LIST(projectId as string), (prevData: any) => (prevData ?? []).map((c: any) => ({ ...c, is_favorite: c.id === cycle.id ? true : c.is_favorite, })), false ); cycleService .addCycleToFavorites(workspaceSlug as string, projectId as string, { cycle: cycle.id, }) .catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Couldn't add the cycle to favorites. Please try again.", }); }); }; const handleRemoveFromFavorites = () => { if (!workspaceSlug || !projectId || !cycle) return; mutate( CURRENT_CYCLE_LIST(projectId as string), (prevData) => (prevData ?? []).map((c) => ({ ...c, is_favorite: c.id === cycle.id ? false : c.is_favorite, })), false ); mutate( CYCLES_LIST(projectId as string), (prevData: any) => (prevData ?? []).map((c: any) => ({ ...c, is_favorite: c.id === cycle.id ? false : c.is_favorite, })), false ); cycleService.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id).catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Couldn't remove the cycle from favorites. Please try again.", }); }); }; const progressIndicatorData = stateGroups.map((group, index) => ({ id: index, name: group.title, value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0, color: group.color, })); return (

{truncateText(cycle.name, 70)}

{cycleStatus === "current" ? ( {findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left ) : cycleStatus === "upcoming" ? ( {findHowManyDaysLeft(cycle.start_date ?? new Date())} Days Left ) : cycleStatus === "completed" ? ( {cycle.total_issues - cycle.completed_issues > 0 && ( )}{" "} Completed ) : ( cycleStatus )} {cycle.is_favorite ? ( ) : ( )}
{renderShortDateWithYearFormat(startDate)}
{renderShortDateWithYearFormat(endDate)}
{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}
{cycle.assignees.length > 0 && (
)}
{cycle.total_issues} issues
{cycle.completed_issues} issues
View Cycle
Progress
{Object.keys(groupedIssues).map((group, index) => ( {group}
} completed={groupedIssues[group]} total={cycle.total_issues} /> ))}
High Priority Issues
{issues ? ( issues.length > 0 ? ( issues.map((issue) => (
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)} className="flex flex-wrap cursor-pointer rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5" >
{issue.project_detail?.identifier}-{issue.sequence_id}
{truncateText(issue.name, 30)}
{issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
) : ( "" )}
)) ) : (
No issues present in the cycle.
) ) : ( )}
{issues && issues.length > 0 && (
issue?.state_detail?.group === "completed")?.length / issues.length) * 100 ?? 0 }%`, }} />
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of {issues?.length}
)}
Ideal
Current
Pending Issues - {cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)}
); };