diff --git a/web/ce/components/cycles/analytics-sidebar/base.tsx b/web/ce/components/cycles/analytics-sidebar/base.tsx index 34d61efaa5d..87f07e387c0 100644 --- a/web/ce/components/cycles/analytics-sidebar/base.tsx +++ b/web/ce/components/cycles/analytics-sidebar/base.tsx @@ -3,15 +3,14 @@ import { FC, Fragment } from "react"; import { observer } from "mobx-react"; // plane ui import { TCycleEstimateType } from "@plane/types"; -import { EEstimateSystem } from "@plane/types/src/enums"; -import { CustomSelect, Loader } from "@plane/ui"; +import { Loader } from "@plane/ui"; // components import ProgressChart from "@/components/core/sidebar/progress-chart"; -import { cycleEstimateOptions, validateCycleSnapshot } from "@/components/cycles"; +import { EstimateTypeDropdown, validateCycleSnapshot } from "@/components/cycles"; // helpers import { getDate } from "@/helpers/date-time.helper"; // hooks -import { useCycle, useProjectEstimates } from "@/hooks/store"; +import { useCycle } from "@/hooks/store"; type ProgressChartProps = { workspaceSlug: string; @@ -24,7 +23,6 @@ export const SidebarChart: FC = observer((props) => { // hooks const { getEstimateTypeByCycleId, getCycleById, fetchCycleDetails, fetchArchivedCycleDetails, setEstimateType } = useCycle(); - const { currentActiveEstimateId, areEstimateEnabledByProjectId, estimateById } = useProjectEstimates(); // derived data const cycleDetails = validateCycleSnapshot(getCycleById(cycleId)); @@ -33,10 +31,7 @@ export const SidebarChart: FC = observer((props) => { const totalEstimatePoints = cycleDetails?.total_estimate_points || 0; const totalIssues = cycleDetails?.total_issues || 0; const estimateType = getEstimateTypeByCycleId(cycleId); - const isCurrentProjectEstimateEnabled = Boolean(projectId && areEstimateEnabledByProjectId(projectId)); - const estimateDetails = - isCurrentProjectEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId); - const isCurrentEstimateTypeIsPoints = estimateDetails && estimateDetails?.type === EEstimateSystem.POINTS; + const chartDistributionData = estimateType === "points" ? cycleDetails?.estimate_distribution : cycleDetails?.distribution || undefined; @@ -63,23 +58,9 @@ export const SidebarChart: FC = observer((props) => { }; return ( <> - {isCurrentEstimateTypeIsPoints && ( -
- {cycleEstimateOptions.find((v) => v.value === estimateType)?.label ?? "None"}} - onChange={onChange} - maxHeight="lg" - buttonClassName="border-none rounded text-sm font-medium capitalize" - > - {cycleEstimateOptions.map((item) => ( - - {item.label} - - ))} - -
- )} +
+ +
diff --git a/web/core/components/cycles/active-cycle/productivity.tsx b/web/core/components/cycles/active-cycle/productivity.tsx index 1e70f326f40..74957af03c7 100644 --- a/web/core/components/cycles/active-cycle/productivity.tsx +++ b/web/core/components/cycles/active-cycle/productivity.tsx @@ -1,8 +1,8 @@ import { FC, Fragment } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { ICycle, TCycleEstimateType, TCyclePlotType } from "@plane/types"; -import { CustomSelect, Loader } from "@plane/ui"; +import { ICycle, TCycleEstimateType } from "@plane/types"; +import { Loader } from "@plane/ui"; // components import ProgressChart from "@/components/core/sidebar/progress-chart"; import { EmptyState } from "@/components/empty-state"; @@ -11,6 +11,7 @@ import { EmptyStateType } from "@/constants/empty-state"; import { useCycle, useProjectEstimates } from "@/hooks/store"; // plane web constants import { EEstimateSystem } from "@/plane-web/constants/estimates"; +import { EstimateTypeDropdown } from "../dropdowns/estimate-type-dropdown"; export type ActiveCycleProductivityProps = { workspaceSlug: string; @@ -18,16 +19,10 @@ export type ActiveCycleProductivityProps = { cycle: ICycle | null; }; -const cycleBurnDownChartOptions = [ - { value: "issues", label: "Issues" }, - { value: "points", label: "Points" }, -]; - export const ActiveCycleProductivity: FC = observer((props) => { const { workspaceSlug, projectId, cycle } = props; // hooks const { getEstimateTypeByCycleId, setEstimateType } = useCycle(); - const { currentActiveEstimateId, areEstimateEnabledByProjectId, estimateById } = useProjectEstimates(); // derived values const estimateType: TCycleEstimateType = (cycle && getEstimateTypeByCycleId(cycle.id)) || "issues"; @@ -37,11 +32,6 @@ export const ActiveCycleProductivity: FC = observe setEstimateType(cycle.id, value); }; - const isCurrentProjectEstimateEnabled = projectId && areEstimateEnabledByProjectId(projectId) ? true : false; - const estimateDetails = - isCurrentProjectEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId); - const isCurrentEstimateTypeIsPoints = estimateDetails && estimateDetails?.type === EEstimateSystem.POINTS; - const chartDistributionData = cycle && estimateType === "points" ? cycle?.estimate_distribution : cycle?.distribution || undefined; const completionChartDistributionData = chartDistributionData?.completion_chart || undefined; @@ -52,22 +42,7 @@ export const ActiveCycleProductivity: FC = observe

Issue burndown

- {isCurrentEstimateTypeIsPoints && ( -
- {cycleBurnDownChartOptions.find((v) => v.value === estimateType)?.label ?? "None"}} - onChange={onChange} - maxHeight="lg" - > - {cycleBurnDownChartOptions.map((item) => ( - - {item.label} - - ))} - -
- )} +
diff --git a/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx b/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx new file mode 100644 index 00000000000..7eba6418d90 --- /dev/null +++ b/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { TCycleEstimateType } from "@plane/types"; +import { CustomSelect } from "@plane/ui"; +import { useCycle, useProjectEstimates } from "@/hooks/store"; +import { cycleEstimateOptions } from "../analytics-sidebar"; + +type TProps = { + value: TCycleEstimateType; + onChange: (value: TCycleEstimateType) => Promise; + showDefault?: boolean; + projectId: string; + cycleId: string; +}; + +export const EstimateTypeDropdown = (props: TProps) => { + const { value, onChange, projectId, cycleId, showDefault = false } = props; + const { getIsPointsDataAvailable } = useCycle(); + const { areEstimateEnabledByProjectId } = useProjectEstimates(); + const isCurrentProjectEstimateEnabled = projectId && areEstimateEnabledByProjectId(projectId) ? true : false; + return getIsPointsDataAvailable(cycleId) || isCurrentProjectEstimateEnabled ? ( +
+ {cycleEstimateOptions.find((v) => v.value === value)?.label ?? "None"}} + onChange={onChange} + maxHeight="lg" + buttonClassName="bg-custom-background-90 border-none rounded text-sm font-medium " + > + {cycleEstimateOptions.map((item) => ( + + {item.label} + + ))} + +
+ ) : showDefault ? ( + {value} + ) : null; +}; diff --git a/web/core/components/cycles/dropdowns/index.ts b/web/core/components/cycles/dropdowns/index.ts index 302e3a1a6ed..2d1f1155417 100644 --- a/web/core/components/cycles/dropdowns/index.ts +++ b/web/core/components/cycles/dropdowns/index.ts @@ -1 +1,2 @@ export * from "./filters"; +export * from "./estimate-type-dropdown"; diff --git a/web/core/store/cycle.store.ts b/web/core/store/cycle.store.ts index 2f7dd59cfec..d4408d80554 100644 --- a/web/core/store/cycle.store.ts +++ b/web/core/store/cycle.store.ts @@ -1,4 +1,5 @@ import { isFuture, isPast, isToday } from "date-fns"; +import isEmpty from "lodash/isEmpty"; import set from "lodash/set"; import sortBy from "lodash/sortBy"; import { action, computed, observable, makeObservable, runInAction } from "mobx"; @@ -58,6 +59,8 @@ export interface ICycleStore { getProjectCycleIds: (projectId: string) => string[] | null; getPlotTypeByCycleId: (cycleId: string) => TCyclePlotType; getEstimateTypeByCycleId: (cycleId: string) => TCycleEstimateType; + getIsPointsDataAvailable: (cycleId: string) => boolean; + // actions updateCycleDistribution: (distributionUpdates: DistributionUpdates, cycleId: string) => void; validateDate: (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => Promise; @@ -271,6 +274,16 @@ export class CycleStore implements ICycleStore { return this.cycleMap?.[this.currentProjectActiveCycleId!] ?? null; } + getIsPointsDataAvailable = computedFn((cycleId: string) => { + const cycle = this.cycleMap[cycleId]; + if (!cycle) return false; + if (this.cycleMap[cycleId].version === 2) return cycle.progress.some((p) => p.total_estimate_points > 0); + else if (this.cycleMap[cycleId].version === 1) { + const completionChart = cycle.estimate_distribution?.completion_chart || {}; + return !isEmpty(completionChart) && Object.keys(completionChart).some((p) => completionChart[p]! > 0); + } else return false; + }); + /** * returns active cycle progress for a project */