diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index 69436c2e836..e857dc1a493 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -31,3 +31,4 @@ export * from "./favorite-folder-icon"; export * from "./planned-icon"; export * from "./in-progress-icon"; export * from "./done-icon"; +export * from "./pending-icon"; diff --git a/packages/ui/src/icons/pending-icon.tsx b/packages/ui/src/icons/pending-icon.tsx new file mode 100644 index 00000000000..5269a22e2b0 --- /dev/null +++ b/packages/ui/src/icons/pending-icon.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; + +import { ISvgIcons } from "./type"; + +export const PendingState: React.FC = ({ width = "10", height = "11", className, color = "#455068" }) => ( + + + + +); diff --git a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx index 72dae40b1e0..4edf41bbdba 100644 --- a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx @@ -16,7 +16,7 @@ export const WorkspaceActiveCycleHeader = observer(() => ( type="text" link={ } /> } diff --git a/web/ce/components/cycles/analytics-sidebar/base.tsx b/web/ce/components/cycles/analytics-sidebar/base.tsx index 94609bc1f63..34d61efaa5d 100644 --- a/web/ce/components/cycles/analytics-sidebar/base.tsx +++ b/web/ce/components/cycles/analytics-sidebar/base.tsx @@ -2,14 +2,16 @@ import { FC, Fragment } from "react"; import { observer } from "mobx-react"; // plane ui -import { Loader } from "@plane/ui"; +import { TCycleEstimateType } from "@plane/types"; +import { EEstimateSystem } from "@plane/types/src/enums"; +import { CustomSelect, Loader } from "@plane/ui"; // components import ProgressChart from "@/components/core/sidebar/progress-chart"; -import { validateCycleSnapshot } from "@/components/cycles"; +import { cycleEstimateOptions, validateCycleSnapshot } from "@/components/cycles"; // helpers import { getDate } from "@/helpers/date-time.helper"; // hooks -import { useCycle } from "@/hooks/store"; +import { useCycle, useProjectEstimates } from "@/hooks/store"; type ProgressChartProps = { workspaceSlug: string; @@ -20,7 +22,9 @@ export const SidebarChart: FC = observer((props) => { const { workspaceSlug, projectId, cycleId } = props; // hooks - const { getEstimateTypeByCycleId, getCycleById } = useCycle(); + const { getEstimateTypeByCycleId, getCycleById, fetchCycleDetails, fetchArchivedCycleDetails, setEstimateType } = + useCycle(); + const { currentActiveEstimateId, areEstimateEnabledByProjectId, estimateById } = useProjectEstimates(); // derived data const cycleDetails = validateCycleSnapshot(getCycleById(cycleId)); @@ -29,7 +33,10 @@ 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; @@ -37,33 +44,71 @@ export const SidebarChart: FC = observer((props) => { if (!workspaceSlug || !projectId || !cycleId) return null; + const isArchived = !!cycleDetails?.archived_at; + + // handlers + const onChange = async (value: TCycleEstimateType) => { + setEstimateType(cycleId, value); + if (!workspaceSlug || !projectId || !cycleId) return; + try { + if (isArchived) { + await fetchArchivedCycleDetails(workspaceSlug, projectId, cycleId); + } else { + await fetchCycleDetails(workspaceSlug, projectId, cycleId); + } + } catch (err) { + console.error(err); + setEstimateType(cycleId, estimateType); + } + }; return ( -
-
-
- - Ideal + <> + {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} + + ))} +
-
- - Current + )} +
+
+
+
+ + Ideal +
+
+ + Current +
+
+ {cycleStartDate && cycleEndDate && completionChartDistributionData ? ( + + + + ) : ( + + + + )}
- {cycleStartDate && cycleEndDate && completionChartDistributionData ? ( - - - - ) : ( - - - - )} -
+ ); }); diff --git a/web/core/components/cycles/analytics-sidebar/issue-progress.tsx b/web/core/components/cycles/analytics-sidebar/issue-progress.tsx index 55178ed2171..d9725d1d66d 100644 --- a/web/core/components/cycles/analytics-sidebar/issue-progress.tsx +++ b/web/core/components/cycles/analytics-sidebar/issue-progress.tsx @@ -7,8 +7,7 @@ import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import { ChevronUp, ChevronDown } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; -import { ICycle, IIssueFilterOptions, TCycleEstimateType, TCyclePlotType, TProgressSnapshot } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; +import { ICycle, IIssueFilterOptions, TCyclePlotType, TProgressSnapshot } from "@plane/types"; // components import { CycleProgressStats } from "@/components/cycles"; // constants @@ -25,6 +24,19 @@ type TCycleAnalyticsProgress = { projectId: string; cycleId: string; }; +type Options = { + value: string; + label: string; +}; + +export const cycleEstimateOptions: Options[] = [ + { value: "issues", label: "Issues" }, + { value: "points", label: "Points" }, +]; +export const cycleChartOptions: Options[] = [ + { value: "burndown", label: "Burn-down" }, + { value: "burnup", label: "Burn-up" }, +]; export const validateCycleSnapshot = (cycleDetails: ICycle | null): ICycle | null => { if (!cycleDetails || cycleDetails === null) return cycleDetails; @@ -41,32 +53,13 @@ export const validateCycleSnapshot = (cycleDetails: ICycle | null): ICycle | nul return updatedCycleDetails; }; -type options = { - value: string; - label: string; -}; -export const cycleChartOptions: options[] = [ - { value: "burndown", label: "Burn-down" }, - { value: "burnup", label: "Burn-up" }, -]; -export const cycleEstimateOptions: options[] = [ - { value: "issues", label: "issues" }, - { value: "points", label: "points" }, -]; export const CycleAnalyticsProgress: FC = observer((props) => { // props const { workspaceSlug, projectId, cycleId } = props; // router const searchParams = useSearchParams(); const peekCycle = searchParams.get("peekCycle") || undefined; - const { - getPlotTypeByCycleId, - getEstimateTypeByCycleId, - getCycleById, - fetchCycleDetails, - fetchArchivedCycleDetails, - setEstimateType, - } = useCycle(); + const { getPlotTypeByCycleId, getEstimateTypeByCycleId, getCycleById } = useCycle(); const { issuesFilter: { issueFilters, updateFilters }, } = useIssues(EIssuesStoreType.CYCLE); @@ -76,21 +69,9 @@ export const CycleAnalyticsProgress: FC = observer((pro const plotType: TCyclePlotType = getPlotTypeByCycleId(cycleId); const estimateType = getEstimateTypeByCycleId(cycleId); - const completedIssues = cycleDetails?.completed_issues || 0; const totalIssues = cycleDetails?.total_issues || 0; - const completedEstimatePoints = cycleDetails?.completed_estimate_points || 0; const totalEstimatePoints = cycleDetails?.total_estimate_points || 0; - const progressHeaderPercentage = cycleDetails - ? estimateType === "points" - ? completedEstimatePoints != 0 && totalEstimatePoints != 0 - ? Math.round((completedEstimatePoints / totalEstimatePoints) * 100) - : 0 - : completedIssues != 0 && completedIssues != 0 - ? Math.round((completedIssues / totalIssues) * 100) - : 0 - : 0; - const chartDistributionData = estimateType === "points" ? cycleDetails?.estimate_distribution : cycleDetails?.distribution || undefined; @@ -115,23 +96,6 @@ export const CycleAnalyticsProgress: FC = observer((pro const isCycleStartDateValid = cycleStartDate && cycleStartDate <= new Date(); const isCycleEndDateValid = cycleStartDate && cycleEndDate && cycleEndDate >= cycleStartDate; const isCycleDateValid = isCycleStartDateValid && isCycleEndDateValid; - const isArchived = !!cycleDetails?.archived_at; - - // handlers - const onChange = async (value: TCycleEstimateType) => { - setEstimateType(cycleId, value); - if (!workspaceSlug || !projectId || !cycleId) return; - try { - if (isArchived) { - await fetchArchivedCycleDetails(workspaceSlug, projectId, cycleId); - } else { - await fetchCycleDetails(workspaceSlug, projectId, cycleId); - } - } catch (err) { - console.error(err); - setEstimateType(cycleId, estimateType); - } - }; const handleFiltersUpdate = useCallback( (key: keyof IIssueFilterOptions, value: string | string[]) => { @@ -192,30 +156,7 @@ export const CycleAnalyticsProgress: FC = observer((pro -
- {cycleEstimateOptions.find((v) => v.value === estimateType)?.label ?? "None"}} - onChange={onChange} - maxHeight="lg" - buttonClassName="border-none rounded text-sm font-medium" - > - {cycleEstimateOptions.map((item) => ( - - {item.label} - - ))} - -
-
- Done - {progressHeaderPercentage}% -
-
-
-
- -
+ {/* progress detailed view */} {chartDistributionData && (
diff --git a/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx b/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx index 6a654e0535a..af645804eb7 100644 --- a/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx +++ b/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx @@ -240,7 +240,7 @@ export const CycleSidebarHeader: FC = observer((props) => {

Archive cycle

- Only completed cycle
can be archived. + Only completed cycles
can be archived.

diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index eab9c501898..989e0436e36 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -211,7 +211,7 @@ export const CycleListItemAction: FC = observer((props) => { <>