import { MouseEvent } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; // hooks import { useApplication, useCycle, useIssues, useProjectState } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { SingleProgressStats } from "components/core"; import { AvatarGroup, Loader, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon, LayersIcon, StateGroupIcon, PriorityIcon, Avatar, } from "@plane/ui"; // components import ProgressChart from "components/core/sidebar/progress-chart"; import { ActiveCycleProgressStats } from "components/cycles"; import { ViewIssueLabel } from "components/issues"; // icons import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; // helpers import { renderFormattedDate, findHowManyDaysLeft } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle } from "@plane/types"; import { EIssuesStoreType } from "constants/issue"; import { ACTIVE_CYCLE_ISSUES } from "store/issue/cycle"; import { 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", }, ]; interface IActiveCycleDetails { workspaceSlug: string; projectId: string; } export const ActiveCycleDetails: React.FC = observer((props) => { // props const { workspaceSlug, projectId } = props; // store hooks const { issues: { issues, fetchActiveCycleIssues }, issueMap, } = useIssues(EIssuesStoreType.CYCLE); const { commandPalette: { toggleCreateCycleModal }, } = useApplication(); const { fetchActiveCycle, currentProjectActiveCycleId, getActiveCycleById, addCycleToFavorites, removeCycleFromFavorites, } = useCycle(); const { getProjectStates } = useProjectState(); // 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 issueIds = issues?.[ACTIVE_CYCLE_ISSUES]; useSWR( workspaceSlug && projectId && currentProjectActiveCycleId ? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" }) : null, workspaceSlug && projectId && currentProjectActiveCycleId ? () => fetchActiveCycleIssues(workspaceSlug, projectId, currentProjectActiveCycleId) : null ); if (!activeCycle && isLoading) return ( ); if (!activeCycle) return (

No active cycle

); 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.toLocaleLowerCase(); 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 = stateGroups.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, })); return (

{truncateText(activeCycle.name, 70)}

{cycleStatus === "current" ? ( {findHowManyDaysLeft(activeCycle.end_date ?? new Date())} Days Left ) : cycleStatus === "upcoming" ? ( {findHowManyDaysLeft(activeCycle.start_date ?? new Date())} Days Left ) : cycleStatus === "completed" ? ( {activeCycle.total_issues - activeCycle.completed_issues > 0 && ( )}{" "} Completed ) : ( cycleStatus )} {activeCycle.is_favorite ? ( ) : ( )}
{renderFormattedDate(startDate)}
{renderFormattedDate(endDate)}
{activeCycle.owned_by.avatar && activeCycle.owned_by.avatar !== "" ? ( {activeCycle.owned_by.display_name} ) : ( {activeCycle.owned_by.display_name.charAt(0)} )} {activeCycle.owned_by.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
{issueIds ? ( issueIds.length > 0 ? ( issueIds.map((issue: any) => (
{issue.project_detail?.identifier}-{issue.sequence_id}
{truncateText(issue.name, 30)}
{issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
{issue.assignee_details.map((assignee: any) => ( ))}
) : ( "" )}
)) ) : (
No issues present in the cycle.
) ) : ( )}
{issueIds && issueIds.length > 0 && (
issue?.state_detail?.group === "completed")?.length / issueIds.length) * 100 ?? 0 }%`, }} />
of{" "} { issueIds?.filter( (issueId) => getProjectStates(issueMap[issueId]?.project_id).find( (issue) => issue.id === issueMap[issueId]?.state_id )?.group === "completed" )?.length }{" "} of {issueIds?.length}
)}
Ideal
Current
Pending Issues -{" "} {activeCycle.total_issues - (activeCycle.completed_issues + activeCycle.cancelled_issues)}
); });