[WEB-2007] fix: cycles loading optimization (#5207)

* fix: cycles loading optimization

* fix: ts error

* fix: types added along with apis

* fix: formatting

* fix: removed bottom border

* fix: fixed loading state for cycle-stats

---------

Co-authored-by: gakshita <akshitagoyal1516@gmail.com>
This commit is contained in:
Satish Gandham 2024-07-23 17:04:31 +05:30 committed by GitHub
parent 2978593c63
commit 31fe9a1a02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 223 additions and 100 deletions

View file

@ -1,4 +1,4 @@
import type { TIssue, IIssueFilterOptions } from "@plane/types"; import type {TIssue, IIssueFilterOptions} from "@plane/types";
export type TCycleGroups = "current" | "upcoming" | "completed" | "draft"; export type TCycleGroups = "current" | "upcoming" | "completed" | "draft";
@ -61,6 +61,10 @@ export type TProgressSnapshot = {
estimate_distribution?: TCycleEstimateDistribution; estimate_distribution?: TCycleEstimateDistribution;
}; };
export interface IProjectDetails {
id: string;
}
export interface ICycle extends TProgressSnapshot { export interface ICycle extends TProgressSnapshot {
progress_snapshot: TProgressSnapshot | undefined; progress_snapshot: TProgressSnapshot | undefined;
@ -85,6 +89,7 @@ export interface ICycle extends TProgressSnapshot {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
}; };
workspace_id: string; workspace_id: string;
project_detail: IProjectDetails;
} }
export interface CycleIssueResponse { export interface CycleIssueResponse {
@ -102,7 +107,7 @@ export interface CycleIssueResponse {
} }
export type SelectCycleType = export type SelectCycleType =
| (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | (ICycle & {actionType: "edit" | "delete" | "create-issue"})
| undefined; | undefined;
export type CycleDateCheckData = { export type CycleDateCheckData = {

View file

@ -1,5 +1,6 @@
import { EUserWorkspaceRoles } from "@/constants/workspace"; import {EUserWorkspaceRoles} from "@/constants/workspace";
import type { import type {
ICycle,
IProjectMember, IProjectMember,
IUser, IUser,
IUserLite, IUserLite,
@ -46,7 +47,7 @@ export interface IWorkspaceMemberInvitation {
} }
export interface IWorkspaceBulkInviteFormData { export interface IWorkspaceBulkInviteFormData {
emails: { email: string; role: EUserWorkspaceRoles }[]; emails: {email: string; role: EUserWorkspaceRoles}[];
} }
export type Properties = { export type Properties = {
@ -197,3 +198,25 @@ export interface IProductUpdateResponse {
eyes: number; eyes: number;
}; };
} }
export interface IWorkspaceActiveCyclesResponse {
count: number;
extra_stats: null;
next_cursor: string;
next_page_results: boolean;
prev_cursor: string;
prev_page_results: boolean;
results: ICycle[];
total_pages: number;
}
export interface IWorkspaceProgressResponse {
completed_issues: number;
total_issues: number;
started_issues: number;
cancelled_issues: number;
unstarted_issues: number;
}
export interface IWorkspaceAnalyticsResponse {
completion_chart: any;
}

View file

@ -30,11 +30,12 @@ import useLocalStorage from "@/hooks/use-local-storage";
export type ActiveCycleStatsProps = { export type ActiveCycleStatsProps = {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
cycle: ICycle; cycle: ICycle | null;
cycleId?: string | null;
}; };
export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => { export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
const { workspaceSlug, projectId, cycle } = props; const { workspaceSlug, projectId, cycle, cycleId } = props;
const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees"); const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees");
@ -63,22 +64,29 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
useSWR( useSWR(
workspaceSlug && projectId && cycle.id ? CYCLE_ISSUES_WITH_PARAMS(cycle.id, { priority: "urgent,high" }) : null, workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId, { priority: "urgent,high" }) : null,
workspaceSlug && projectId && cycle.id workspaceSlug && projectId && cycleId ? () => fetchActiveCycleIssues(workspaceSlug, projectId, 30, cycleId) : null,
? () => fetchActiveCycleIssues(workspaceSlug, projectId, 30, cycle.id)
: null,
{ revalidateIfStale: false, revalidateOnFocus: false } { revalidateIfStale: false, revalidateOnFocus: false }
); );
const cycleIssueDetails = getActiveCycleById(cycle.id); const cycleIssueDetails = cycleId ? getActiveCycleById(cycleId) : { nextPageResults: false };
const loadMoreIssues = useCallback(() => { const loadMoreIssues = useCallback(() => {
fetchNextActiveCycleIssues(workspaceSlug, projectId, cycle.id); if (!cycleId) return;
}, [workspaceSlug, projectId, cycle.id, issuesLoaderElement, cycleIssueDetails?.nextPageResults]); fetchNextActiveCycleIssues(workspaceSlug, projectId, cycleId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceSlug, projectId, cycleId, issuesLoaderElement, cycleIssueDetails?.nextPageResults]);
useIntersectionObserver(issuesContainerRef, issuesLoaderElement, loadMoreIssues, `0% 0% 100% 0%`); useIntersectionObserver(issuesContainerRef, issuesLoaderElement, loadMoreIssues, `0% 0% 100% 0%`);
return ( const loaders = (
<Loader className="space-y-3">
<Loader.Item height="30px" />
<Loader.Item height="30px" />
<Loader.Item height="30px" />
</Loader>
);
return cycleId ? (
<div className="flex flex-col gap-4 p-4 min-h-[17rem] overflow-hidden bg-custom-background-100 col-span-1 lg:col-span-2 xl:col-span-1 border border-custom-border-200 rounded-lg"> <div className="flex flex-col gap-4 p-4 min-h-[17rem] overflow-hidden bg-custom-background-100 col-span-1 lg:col-span-2 xl:col-span-1 border border-custom-border-200 rounded-lg">
<Tab.Group <Tab.Group
as={Fragment} as={Fragment}
@ -154,7 +162,7 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
ref={issuesContainerRef} ref={issuesContainerRef}
className="flex flex-col gap-1 h-full w-full overflow-y-auto vertical-scrollbar scrollbar-sm" className="flex flex-col gap-1 h-full w-full overflow-y-auto vertical-scrollbar scrollbar-sm"
> >
{cycleIssueDetails && cycleIssueDetails.issueIds ? ( {cycleIssueDetails && "issueIds" in cycleIssueDetails ? (
cycleIssueDetails.issueCount > 0 ? ( cycleIssueDetails.issueCount > 0 ? (
<> <>
{cycleIssueDetails.issueIds.map((issueId: string) => { {cycleIssueDetails.issueIds.map((issueId: string) => {
@ -229,11 +237,7 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
</div> </div>
) )
) : ( ) : (
<Loader className="space-y-3"> loaders
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" />
</Loader>
)} )}
</div> </div>
</Tab.Panel> </Tab.Panel>
@ -242,44 +246,52 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
as="div" as="div"
className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm" className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm"
> >
{cycle?.distribution?.assignees && cycle.distribution.assignees.length > 0 ? ( {cycle ? (
cycle.distribution?.assignees?.map((assignee, index) => { cycle?.distribution?.assignees && cycle.distribution.assignees.length > 0 ? (
if (assignee.assignee_id) cycle.distribution?.assignees?.map((assignee, index) => {
return ( if (assignee.assignee_id)
<SingleProgressStats return (
key={assignee.assignee_id} <SingleProgressStats
title={ key={assignee.assignee_id}
<div className="flex items-center gap-2"> title={
<Avatar name={assignee?.display_name ?? undefined} src={assignee?.avatar ?? undefined} /> <div className="flex items-center gap-2">
<Avatar name={assignee?.display_name ?? undefined} src={assignee?.avatar ?? undefined} />
<span>{assignee.display_name}</span> <span>{assignee.display_name}</span>
</div>
}
completed={assignee.completed_issues}
total={assignee.total_issues}
/>
);
else
return (
<SingleProgressStats
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-200 bg-custom-background-80">
<img src="/user.png" height="100%" width="100%" className="rounded-full" alt="User" />
</div> </div>
<span>No assignee</span> }
</div> completed={assignee.completed_issues}
} total={assignee.total_issues}
completed={assignee.completed_issues} />
total={assignee.total_issues} );
/> else
); return (
}) <SingleProgressStats
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-200 bg-custom-background-80">
<img src="/user.png" height="100%" width="100%" className="rounded-full" alt="User" />
</div>
<span>No assignee</span>
</div>
}
completed={assignee.completed_issues}
total={assignee.total_issues}
/>
);
})
) : (
<div className="flex items-center justify-center h-full w-full">
<EmptyState
type={EmptyStateType.ACTIVE_CYCLE_ASSIGNEE_EMPTY_STATE}
layout="screen-simple"
size="sm"
/>
</div>
)
) : ( ) : (
<div className="flex items-center justify-center h-full w-full"> loaders
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_ASSIGNEE_EMPTY_STATE} layout="screen-simple" size="sm" />
</div>
)} )}
</Tab.Panel> </Tab.Panel>
@ -287,33 +299,41 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
as="div" as="div"
className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm" className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm"
> >
{cycle?.distribution?.labels && cycle.distribution.labels.length > 0 ? ( {cycle ? (
cycle.distribution.labels?.map((label, index) => ( cycle?.distribution?.labels && cycle.distribution.labels.length > 0 ? (
<SingleProgressStats cycle.distribution.labels?.map((label, index) => (
key={label.label_id ?? `no-label-${index}`} <SingleProgressStats
title={ key={label.label_id ?? `no-label-${index}`}
<div className="flex items-center gap-2"> title={
<span <div className="flex items-center gap-2">
className="block h-3 w-3 rounded-full" <span
style={{ className="block h-3 w-3 rounded-full"
backgroundColor: label.color ?? "#000000", style={{
}} backgroundColor: label.color ?? "#000000",
/> }}
<span className="text-xs">{label.label_name ?? "No labels"}</span> />
</div> <span className="text-xs">{label.label_name ?? "No labels"}</span>
} </div>
completed={label.completed_issues} }
total={label.total_issues} completed={label.completed_issues}
/> total={label.total_issues}
)) />
))
) : (
<div className="flex items-center justify-center h-full w-full">
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_LABEL_EMPTY_STATE} layout="screen-simple" size="sm" />
</div>
)
) : ( ) : (
<div className="flex items-center justify-center h-full w-full"> loaders
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_LABEL_EMPTY_STATE} layout="screen-simple" size="sm" />
</div>
)} )}
</Tab.Panel> </Tab.Panel>
</Tab.Panels> </Tab.Panels>
</Tab.Group> </Tab.Group>
</div> </div>
) : (
<Loader className="flex flex-col gap-4 min-h-[17rem] overflow-hidden bg-custom-background-100 col-span-1 lg:col-span-2 xl:col-span-1">
<Loader.Item width="100%" height="17rem" />
</Loader>
); );
}); });

View file

@ -2,7 +2,7 @@ import { FC, Fragment, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import Link from "next/link"; import Link from "next/link";
import { ICycle, TCyclePlotType } from "@plane/types"; import { ICycle, TCyclePlotType } from "@plane/types";
import { CustomSelect, Spinner } from "@plane/ui"; import { CustomSelect, Loader, Spinner } from "@plane/ui";
// components // components
import ProgressChart from "@/components/core/sidebar/progress-chart"; import ProgressChart from "@/components/core/sidebar/progress-chart";
import { EmptyState } from "@/components/empty-state"; import { EmptyState } from "@/components/empty-state";
@ -15,7 +15,7 @@ import { EEstimateSystem } from "@/plane-web/constants/estimates";
export type ActiveCycleProductivityProps = { export type ActiveCycleProductivityProps = {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
cycle: ICycle; cycle: ICycle | null;
}; };
const cycleBurnDownChartOptions = [ const cycleBurnDownChartOptions = [
@ -51,10 +51,11 @@ export const ActiveCycleProductivity: FC<ActiveCycleProductivityProps> = observe
isCurrentProjectEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId); isCurrentProjectEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId);
const isCurrentEstimateTypeIsPoints = estimateDetails && estimateDetails?.type === EEstimateSystem.POINTS; const isCurrentEstimateTypeIsPoints = estimateDetails && estimateDetails?.type === EEstimateSystem.POINTS;
const chartDistributionData = plotType === "points" ? cycle?.estimate_distribution : cycle?.distribution || undefined; const chartDistributionData =
cycle && plotType === "points" ? cycle?.estimate_distribution : cycle?.distribution || undefined;
const completionChartDistributionData = chartDistributionData?.completion_chart || undefined; const completionChartDistributionData = chartDistributionData?.completion_chart || undefined;
return ( return cycle ? (
<div className="flex flex-col justify-center min-h-[17rem] gap-5 px-3.5 py-4 bg-custom-background-100 border border-custom-border-200 rounded-lg"> <div className="flex flex-col justify-center min-h-[17rem] gap-5 px-3.5 py-4 bg-custom-background-100 border border-custom-border-200 rounded-lg">
<div className="relative flex items-center justify-between gap-4 -mt-7"> <div className="relative flex items-center justify-between gap-4 -mt-7">
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`}> <Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`}>
@ -135,5 +136,9 @@ export const ActiveCycleProductivity: FC<ActiveCycleProductivityProps> = observe
)} )}
</Link> </Link>
</div> </div>
) : (
<Loader className="flex flex-col min-h-[17rem] gap-5 bg-custom-background-100 border border-custom-border-200 rounded-lg">
<Loader.Item width="100%" height="100%" />
</Loader>
); );
}); });

View file

@ -5,7 +5,7 @@ import Link from "next/link";
// types // types
import { ICycle } from "@plane/types"; import { ICycle } from "@plane/types";
// ui // ui
import { LinearProgressIndicator } from "@plane/ui"; import { LinearProgressIndicator, Loader } from "@plane/ui";
// components // components
import { EmptyState } from "@/components/empty-state"; import { EmptyState } from "@/components/empty-state";
// constants // constants
@ -15,7 +15,7 @@ import { EmptyStateType } from "@/constants/empty-state";
export type ActiveCycleProgressProps = { export type ActiveCycleProgressProps = {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
cycle: ICycle; cycle: ICycle | null;
}; };
export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => { export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
@ -24,18 +24,20 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({ const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({
id: index, id: index,
name: group.title, name: group.title,
value: cycle.total_issues > 0 ? (cycle[group.key as keyof ICycle] as number) : 0, value: cycle && cycle.total_issues > 0 ? (cycle[group.key as keyof ICycle] as number) : 0,
color: group.color, color: group.color,
})); }));
const groupedIssues: any = { const groupedIssues: any = cycle
completed: cycle.completed_issues, ? {
started: cycle.started_issues, completed: cycle.completed_issues,
unstarted: cycle.unstarted_issues, started: cycle.started_issues,
backlog: cycle.backlog_issues, unstarted: cycle.unstarted_issues,
}; backlog: cycle.backlog_issues,
}
: {};
return ( return cycle ? (
<Link <Link
href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`} href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`}
className="flex flex-col min-h-[17rem] gap-5 py-4 px-3.5 bg-custom-background-100 border border-custom-border-200 rounded-lg" className="flex flex-col min-h-[17rem] gap-5 py-4 px-3.5 bg-custom-background-100 border border-custom-border-200 rounded-lg"
@ -94,5 +96,9 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = (props) => {
</div> </div>
)} )}
</Link> </Link>
) : (
<Loader className="flex flex-col min-h-[17rem] gap-5 bg-custom-background-100 border border-custom-border-200 rounded-lg">
<Loader.Item width="100%" height="100%" />
</Loader>
); );
}; };

View file

@ -28,7 +28,7 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
// props // props
const { workspaceSlug, projectId } = props; const { workspaceSlug, projectId } = props;
// store hooks // store hooks
const { fetchActiveCycle, currentProjectActiveCycleId, getActiveCycleById } = useCycle(); const { currentProjectActiveCycle, fetchActiveCycle, currentProjectActiveCycleId, getActiveCycleById } = useCycle();
// derived values // derived values
const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null; const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null;
// fetch active cycle details // fetch active cycle details
@ -38,7 +38,7 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
); );
// show loader if active cycle is loading // show loader if active cycle is loading
if (!activeCycle && isLoading) if (!currentProjectActiveCycle && isLoading)
return ( return (
<Loader> <Loader>
<Loader.Item height="250px" /> <Loader.Item height="250px" />
@ -54,10 +54,10 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
<CycleListGroupHeader title="Active cycle" type="current" isExpanded={open} /> <CycleListGroupHeader title="Active cycle" type="current" isExpanded={open} />
</Disclosure.Button> </Disclosure.Button>
<Disclosure.Panel> <Disclosure.Panel>
{!activeCycle ? ( {!currentProjectActiveCycle ? (
<EmptyState type={EmptyStateType.PROJECT_CYCLE_ACTIVE} size="sm" /> <EmptyState type={EmptyStateType.PROJECT_CYCLE_ACTIVE} size="sm" />
) : ( ) : (
<div className="flex flex-col bg-custom-background-90 border-b"> <div className="flex flex-col bg-custom-background-90">
{currentProjectActiveCycleId && ( {currentProjectActiveCycleId && (
<CyclesListItem <CyclesListItem
key={currentProjectActiveCycleId} key={currentProjectActiveCycleId}
@ -75,7 +75,12 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
projectId={projectId} projectId={projectId}
cycle={activeCycle} cycle={activeCycle}
/> />
<ActiveCycleStats workspaceSlug={workspaceSlug} projectId={projectId} cycle={activeCycle} /> <ActiveCycleStats
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
cycleId={currentProjectActiveCycleId}
/>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,15 +1,64 @@
// services // services
import type { CycleDateCheckData, ICycle, TIssuesResponse } from "@plane/types"; import type {
CycleDateCheckData,
ICycle,
TIssuesResponse,
IWorkspaceActiveCyclesResponse,
IWorkspaceProgressResponse,
IWorkspaceAnalyticsResponse,
} from "@plane/types";
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
import { APIService } from "@/services/api.service"; import { APIService } from "@/services/api.service";
// types
// helpers
export class CycleService extends APIService { export class CycleService extends APIService {
constructor() { constructor() {
super(API_BASE_URL); super(API_BASE_URL);
} }
async workspaceActiveCyclesAnalytics(
workspaceSlug: string,
projectId: string,
cycleId: string,
analytic_type: string = "points"
): Promise<IWorkspaceAnalyticsResponse> {
return this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/analytics?type=${analytic_type}`
)
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
async workspaceActiveCyclesProgress(
workspaceSlug: string,
projectId: string,
cycleId: string
): Promise<IWorkspaceProgressResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/progress/`)
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
async workspaceActiveCycles(
workspaceSlug: string,
cursor: string,
per_page: number
): Promise<IWorkspaceActiveCyclesResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/active-cycles/`, {
params: {
per_page,
cursor,
},
})
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
async getWorkspaceCycles(workspaceSlug: string): Promise<ICycle[]> { async getWorkspaceCycles(workspaceSlug: string): Promise<ICycle[]> {
return this.get(`/api/workspaces/${workspaceSlug}/cycles/`) return this.get(`/api/workspaces/${workspaceSlug}/cycles/`)
.then((response) => response?.data) .then((response) => response?.data)

View file

@ -32,6 +32,8 @@ export interface ICycleStore {
currentProjectDraftCycleIds: string[] | null; currentProjectDraftCycleIds: string[] | null;
currentProjectActiveCycleId: string | null; currentProjectActiveCycleId: string | null;
currentProjectArchivedCycleIds: string[] | null; currentProjectArchivedCycleIds: string[] | null;
currentProjectActiveCycle: ICycle | null;
// computed actions // computed actions
getFilteredCycleIds: (projectId: string, sortByManual: boolean) => string[] | null; getFilteredCycleIds: (projectId: string, sortByManual: boolean) => string[] | null;
getFilteredCompletedCycleIds: (projectId: string) => string[] | null; getFilteredCompletedCycleIds: (projectId: string) => string[] | null;
@ -100,6 +102,8 @@ export class CycleStore implements ICycleStore {
currentProjectDraftCycleIds: computed, currentProjectDraftCycleIds: computed,
currentProjectActiveCycleId: computed, currentProjectActiveCycleId: computed,
currentProjectArchivedCycleIds: computed, currentProjectArchivedCycleIds: computed,
currentProjectActiveCycle: computed,
// actions // actions
setPlotType: action, setPlotType: action,
fetchWorkspaceCycles: action, fetchWorkspaceCycles: action,
@ -208,7 +212,7 @@ export class CycleStore implements ICycleStore {
get currentProjectActiveCycleId() { get currentProjectActiveCycleId() {
const projectId = this.rootStore.router.projectId; const projectId = this.rootStore.router.projectId;
if (!projectId) return null; if (!projectId) return null;
const activeCycle = Object.keys(this.activeCycleIdMap ?? {}).find( const activeCycle = Object.keys(this.cycleMap ?? {}).find(
(cycleId) => this.cycleMap?.[cycleId]?.project_id === projectId (cycleId) => this.cycleMap?.[cycleId]?.project_id === projectId
); );
return activeCycle || null; return activeCycle || null;
@ -228,6 +232,12 @@ export class CycleStore implements ICycleStore {
return archivedCycleIds; return archivedCycleIds;
} }
get currentProjectActiveCycle() {
const projectId = this.rootStore.router.projectId;
if (!projectId && !this.currentProjectActiveCycleId) return null;
return this.cycleMap?.[this.currentProjectActiveCycleId!] ?? null;
}
/** /**
* @description returns filtered cycle ids based on display filters and filters * @description returns filtered cycle ids based on display filters and filters
* @param {TCycleDisplayFilters} displayFilters * @param {TCycleDisplayFilters} displayFilters