import { MouseEvent } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; import { useTheme } from "next-themes"; // hooks import { useCycle, useIssues, useMember, useProject, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { SingleProgressStats } from "components/core"; import { AvatarGroup, Loader, Tooltip, LinearProgressIndicator, LayersIcon, StateGroupIcon, PriorityIcon, Avatar, CycleGroupIcon, } from "@plane/ui"; // components import ProgressChart from "components/core/sidebar/progress-chart"; import { ActiveCycleProgressStats } from "components/cycles"; import { StateDropdown } from "components/dropdowns"; import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // icons import { ArrowRight, CalendarCheck, CalendarDays, Star, Target } from "lucide-react"; // helpers import { renderFormattedDate, findHowManyDaysLeft, renderFormattedDateWithoutYear } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle, TCycleGroups } from "@plane/types"; // constants import { EIssuesStoreType } from "constants/issue"; import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; import { CYCLE_EMPTY_STATE_DETAILS, CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle"; interface IActiveCycleDetails { workspaceSlug: string; projectId: string; } export const ActiveCycleDetails: React.FC = observer((props) => { // props const { workspaceSlug, projectId } = props; const { resolvedTheme } = useTheme(); // store hooks const { currentUser } = useUser(); const { issues: { fetchActiveCycleIssues }, } = useIssues(EIssuesStoreType.CYCLE); const { fetchActiveCycle, currentProjectActiveCycleId, getActiveCycleById, addCycleToFavorites, removeCycleFromFavorites, } = useCycle(); const { currentProjectDetails } = useProject(); const { getUserDetails } = useMember(); // toast alert const { setToastAlert } = useToast(); const { isLoading } = useSWR( workspaceSlug && projectId ? `PROJECT_ACTIVE_CYCLE_${projectId}` : null, workspaceSlug && projectId ? () => fetchActiveCycle(workspaceSlug, projectId) : null ); const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null; const cycleOwnerDetails = activeCycle ? getUserDetails(activeCycle.owned_by) : undefined; const { data: activeCycleIssues } = useSWR( workspaceSlug && projectId && currentProjectActiveCycleId ? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" }) : null, workspaceSlug && projectId && currentProjectActiveCycleId ? () => fetchActiveCycleIssues(workspaceSlug, projectId, currentProjectActiveCycleId) : null ); const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["active"]; const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; const emptyStateImage = getEmptyStateImagePath("cycle", "active", isLightMode); if (!activeCycle && isLoading) return ( ); if (!activeCycle) return ( ); const endDate = new Date(activeCycle.end_date ?? ""); const startDate = new Date(activeCycle.start_date ?? ""); const groupedIssues: any = { backlog: activeCycle.backlog_issues, unstarted: activeCycle.unstarted_issues, started: activeCycle.started_issues, completed: activeCycle.completed_issues, cancelled: activeCycle.cancelled_issues, }; const cycleStatus = activeCycle.status.toLowerCase() as TCycleGroups; const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), activeCycle.id).catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Couldn't add the cycle to favorites. Please try again.", }); }); }; const handleRemoveFromFavorites = (e: MouseEvent) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), activeCycle.id).catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Couldn't add the cycle to favorites. Please try again.", }); }); }; const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({ id: index, name: group.title, value: activeCycle.total_issues > 0 ? ((activeCycle[group.key as keyof ICycle] as number) / activeCycle.total_issues) * 100 : 0, color: group.color, })); const daysLeft = findHowManyDaysLeft(activeCycle.end_date ?? new Date()); return (

{truncateText(activeCycle.name, 70)}

{`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`} {activeCycle.is_favorite ? ( ) : ( )}
{renderFormattedDate(startDate)}
{renderFormattedDate(endDate)}
{cycleOwnerDetails?.avatar && cycleOwnerDetails?.avatar !== "" ? ( {cycleOwnerDetails?.display_name} ) : ( {cycleOwnerDetails?.display_name.charAt(0)} )} {cycleOwnerDetails?.display_name}
{activeCycle.assignees.length > 0 && (
{activeCycle.assignees.map((assignee) => ( ))}
)}
{activeCycle.total_issues} issues
{activeCycle.completed_issues} issues
View Cycle
Progress
{Object.keys(groupedIssues).map((group, index) => ( {group}
} completed={groupedIssues[group]} total={activeCycle.total_issues} /> ))}
High Priority Issues
{activeCycleIssues ? ( activeCycleIssues.length > 0 ? ( activeCycleIssues.map((issue: any) => (
{currentProjectDetails?.identifier}-{issue.sequence_id} {truncateText(issue.name, 30)}
{}} projectId={projectId?.toString() ?? ""} disabled={true} buttonVariant="background-with-text" /> {issue.target_date && (
{renderFormattedDateWithoutYear(issue.target_date)}
)}
)) ) : (
There are no high priority issues present in this cycle.
) ) : ( )}
Ideal
Current
Pending Issues -{" "} {activeCycle.total_issues - (activeCycle.completed_issues + activeCycle.cancelled_issues)}
); });