mirror of
https://github.com/gosticks/plane.git
synced 2025-10-16 12:45:33 +00:00
[WEB-4953]fix: cycle progress percentage #7826
This commit is contained in:
parent
97059a2786
commit
f7d5ca4f83
@ -8,7 +8,7 @@ import { Check } from "lucide-react";
|
||||
import type { TCycleGroups } from "@plane/types";
|
||||
import { CircularProgressIndicator } from "@plane/ui";
|
||||
// components
|
||||
import { generateQueryParams } from "@plane/utils";
|
||||
import { generateQueryParams, calculateCycleProgress } from "@plane/utils";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
// hooks
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
@ -50,7 +50,6 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
|
||||
// computed
|
||||
// TODO: change this logic once backend fix the response
|
||||
const cycleStatus = cycleDetails.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft";
|
||||
const isCompleted = cycleStatus === "completed";
|
||||
const isActive = cycleStatus === "current";
|
||||
|
||||
// handlers
|
||||
@ -73,20 +72,7 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
|
||||
|
||||
const handleItemClick = cycleDetails.archived_at ? handleArchivedCycleClick : undefined;
|
||||
|
||||
const getCycleProgress = () => {
|
||||
let completionPercentage =
|
||||
((cycleDetails.completed_issues + cycleDetails.cancelled_issues) / cycleDetails.total_issues) * 100;
|
||||
|
||||
if (isCompleted && !isEmpty(cycleDetails.progress_snapshot)) {
|
||||
completionPercentage =
|
||||
((cycleDetails.progress_snapshot.completed_issues + cycleDetails.progress_snapshot.cancelled_issues) /
|
||||
cycleDetails.progress_snapshot.total_issues) *
|
||||
100;
|
||||
}
|
||||
return isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
|
||||
};
|
||||
|
||||
const progress = getCycleProgress();
|
||||
const progress = calculateCycleProgress(cycleDetails);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
@ -96,16 +82,10 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
|
||||
className={className}
|
||||
prependTitleElement={
|
||||
<CircularProgressIndicator size={30} percentage={progress} strokeWidth={3}>
|
||||
{isCompleted ? (
|
||||
progress === 100 ? (
|
||||
<Check className="h-3 w-3 stroke-[2] text-custom-primary-100" />
|
||||
) : (
|
||||
<span className="text-sm text-custom-primary-100">{`!`}</span>
|
||||
)
|
||||
) : progress === 100 ? (
|
||||
{progress === 100 ? (
|
||||
<Check className="h-3 w-3 stroke-[2] text-custom-primary-100" />
|
||||
) : (
|
||||
<span className="text-[9px] text-custom-text-300">{`${progress}%`}</span>
|
||||
<span className="text-[9px] text-custom-text-100">{`${progress}%`}</span>
|
||||
)}
|
||||
</CircularProgressIndicator>
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import orderBy from "lodash/orderBy";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
// plane imports
|
||||
import { ICycle, TCycleFilters } from "@plane/types";
|
||||
import { ICycle, TCycleFilters, TProgressSnapshot } from "@plane/types";
|
||||
// local imports
|
||||
import { findTotalDaysInRange, generateDateArray, getDate } from "./datetime";
|
||||
import { satisfiesDateFilter } from "./filter";
|
||||
@ -191,3 +191,57 @@ export const formatActiveCycle = (args: {
|
||||
? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate)
|
||||
: formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates cycle progress percentage excluding cancelled issues from total count
|
||||
* Formula: completed / (total - cancelled) * 100
|
||||
* This gives accurate progress based on: pendingIssues = totalIssues - completedIssues - cancelledIssues
|
||||
* @param cycle - Cycle data object
|
||||
* @param estimateType - Whether to calculate based on "issues" or "points"
|
||||
* @param includeInProgress - Whether to include started/in-progress items in completion calculation
|
||||
* @returns Progress percentage (0-100)
|
||||
*/
|
||||
export const calculateCycleProgress = (
|
||||
cycle: ICycle | undefined,
|
||||
estimateType: "issues" | "points" = "issues",
|
||||
includeInProgress: boolean = false
|
||||
): number => {
|
||||
if (!cycle) return 0;
|
||||
|
||||
const progressSnapshot: TProgressSnapshot | undefined = cycle.progress_snapshot;
|
||||
const cycleDetails = progressSnapshot && !isEmpty(progressSnapshot) ? progressSnapshot : cycle;
|
||||
|
||||
let completed: number;
|
||||
let cancelled: number;
|
||||
let total: number;
|
||||
|
||||
if (estimateType === "points") {
|
||||
completed = cycleDetails.completed_estimate_points || 0;
|
||||
cancelled = cycleDetails.cancelled_estimate_points || 0;
|
||||
total = cycleDetails.total_estimate_points || 0;
|
||||
|
||||
if (includeInProgress) {
|
||||
completed += cycleDetails.started_estimate_points || 0;
|
||||
}
|
||||
} else {
|
||||
completed = cycleDetails.completed_issues || 0;
|
||||
cancelled = cycleDetails.cancelled_issues || 0;
|
||||
total = cycleDetails.total_issues || 0;
|
||||
|
||||
if (includeInProgress) {
|
||||
completed += cycleDetails.started_issues || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude cancelled issues from total (pendingIssues = total - completed - cancelled)
|
||||
const adjustedTotal = total - cancelled;
|
||||
|
||||
// Handle edge cases
|
||||
if (adjustedTotal === 0) return 0;
|
||||
if (completed < 0 || adjustedTotal < 0) return 0;
|
||||
if (completed > adjustedTotal) return 100;
|
||||
|
||||
// Calculate percentage and round
|
||||
const percentage = (completed / adjustedTotal) * 100;
|
||||
return Math.round(percentage);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user