mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
refactor: cycles store (#2716)
* refactor: cycles store * refactor: active cycle details
This commit is contained in:
parent
162faf8339
commit
884b219508
@ -75,12 +75,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { isLoading } = useSWR(
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `ACTIVE_CYCLE_ISSUE_${projectId}_CURRENT` : null,
|
||||
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, "current") : null
|
||||
);
|
||||
|
||||
const activeCycle = cycleStore.cycles?.[projectId] || null;
|
||||
const activeCycle = cycleStore.cycles?.[projectId]?.active || null;
|
||||
const cycle = activeCycle ? activeCycle[0] : null;
|
||||
const issues = (cycleStore?.active_cycle_issues as any) || null;
|
||||
|
||||
@ -94,7 +94,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
// : null
|
||||
// ) as { data: IIssue[] | undefined };
|
||||
|
||||
if (isLoading)
|
||||
if (!cycle)
|
||||
return (
|
||||
<Loader>
|
||||
<Loader.Item height="250px" />
|
||||
@ -143,7 +143,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
@ -156,7 +156,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
|
@ -87,7 +87,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
@ -100,7 +100,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
|
@ -12,7 +12,7 @@ export interface ICyclesBoard {
|
||||
filter: string;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
peekCycle: string;
|
||||
peekCycle: string | undefined;
|
||||
}
|
||||
|
||||
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
|
@ -1 +0,0 @@
|
||||
export const CycleGantt = () => <></>;
|
@ -87,7 +87,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
@ -100,7 +100,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
|
@ -15,7 +15,7 @@ export interface ICyclesView {
|
||||
layout: TCycleLayout;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
peekCycle: string;
|
||||
peekCycle: string | undefined;
|
||||
}
|
||||
|
||||
export const CyclesView: FC<ICyclesView> = observer((props) => {
|
||||
@ -30,7 +30,14 @@ export const CyclesView: FC<ICyclesView> = observer((props) => {
|
||||
workspaceSlug && projectId && filter ? () => cycleStore.fetchCycles(workspaceSlug, projectId, filter) : null
|
||||
);
|
||||
|
||||
const cyclesList = cycleStore.cycles?.[projectId];
|
||||
const cyclesList =
|
||||
filter === "completed"
|
||||
? cycleStore.projectCompletedCycles
|
||||
: filter === "draft"
|
||||
? cycleStore.projectDraftCycles
|
||||
: filter === "upcoming"
|
||||
? cycleStore.projectUpcomingCycles
|
||||
: cycleStore.projectCycles;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -5,7 +5,6 @@ export * from "./gantt-chart";
|
||||
export * from "./cycles-view";
|
||||
export * from "./form";
|
||||
export * from "./modal";
|
||||
export * from "./select";
|
||||
export * from "./sidebar";
|
||||
export * from "./transfer-issues-modal";
|
||||
export * from "./transfer-issues";
|
||||
@ -13,7 +12,6 @@ export * from "./cycles-list";
|
||||
export * from "./cycles-list-item";
|
||||
export * from "./cycles-board";
|
||||
export * from "./cycles-board-card";
|
||||
export * from "./cycles-gantt";
|
||||
export * from "./delete-modal";
|
||||
export * from "./cycle-peek-overview";
|
||||
export * from "./cycles-list-item";
|
||||
|
@ -50,7 +50,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
|
||||
const updateCycle = async (cycleId: string, payload: Partial<ICycle>) =>
|
||||
cycleStore
|
||||
.updateCycle(workspaceSlug, projectId, cycleId, payload)
|
||||
.patchCycle(workspaceSlug, projectId, cycleId, payload)
|
||||
.then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
|
@ -1,122 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// icons
|
||||
import { ContrastIcon } from "@plane/ui";
|
||||
import { Plus } from "lucide-react";
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// components
|
||||
import { CycleCreateUpdateModal } from "components/cycles";
|
||||
// fetch-keys
|
||||
import { CYCLES_LIST } from "constants/fetch-keys";
|
||||
|
||||
export type IssueCycleSelectProps = {
|
||||
projectId: string;
|
||||
value: any;
|
||||
onChange: (value: any) => void;
|
||||
multiple?: boolean;
|
||||
};
|
||||
|
||||
const cycleService = new CycleService();
|
||||
|
||||
export const CycleSelect: React.FC<IssueCycleSelectProps> = ({ projectId, value, onChange, multiple = false }) => {
|
||||
// states
|
||||
const [isCycleModalActive, setCycleModalActive] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { data: cycles } = useSWR(
|
||||
workspaceSlug && projectId ? CYCLES_LIST(projectId) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, "all")
|
||||
: null
|
||||
);
|
||||
|
||||
const options = cycles?.map((cycle) => ({ value: cycle.id, display: cycle.name }));
|
||||
|
||||
const openCycleModal = () => {
|
||||
setCycleModalActive(true);
|
||||
};
|
||||
|
||||
const closeCycleModal = () => {
|
||||
setCycleModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{workspaceSlug && projectId && (
|
||||
<CycleCreateUpdateModal
|
||||
isOpen={isCycleModalActive}
|
||||
handleClose={closeCycleModal}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
/>
|
||||
)}
|
||||
<Listbox as="div" className="relative" value={value} onChange={onChange} multiple={multiple}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Button
|
||||
className={`flex cursor-pointer items-center gap-1 rounded-md border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-300 hover:bg-custom-background-90 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500`}
|
||||
>
|
||||
<ContrastIcon className="h-3 w-3 text-custom-text-200" />
|
||||
<div className="flex items-center gap-2 truncate">
|
||||
{cycles?.find((c) => c.id === value)?.name ?? "Cycles"}
|
||||
</div>
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={React.Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options
|
||||
className={`absolute mt-1 max-h-32 min-w-[8rem] overflow-y-auto whitespace-nowrap bg-custom-background-80 shadow-lg text-xs z-10 rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none`}
|
||||
>
|
||||
<div className="py-1">
|
||||
{options ? (
|
||||
options.length > 0 ? (
|
||||
options.map((option) => (
|
||||
<Listbox.Option
|
||||
key={option.value}
|
||||
className={({ selected, active }) =>
|
||||
`${
|
||||
selected || (Array.isArray(value) ? value.includes(option.value) : value === option.value)
|
||||
? "bg-indigo-50 font-medium"
|
||||
: ""
|
||||
} ${
|
||||
active ? "bg-indigo-50" : ""
|
||||
} relative cursor-pointer select-none p-2 text-custom-text-100`
|
||||
}
|
||||
value={option.value}
|
||||
>
|
||||
<span className={` flex items-center gap-2 truncate`}>{option.display}</span>
|
||||
</Listbox.Option>
|
||||
))
|
||||
) : (
|
||||
<p className="text-center text-sm text-custom-text-200">No options</p>
|
||||
)
|
||||
) : (
|
||||
<p className="text-center text-sm text-custom-text-200">Loading...</p>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="relative w-full flex select-none items-center gap-x-2 p-2 text-gray-400 hover:bg-indigo-50 hover:text-custom-text-100"
|
||||
onClick={openCycleModal}
|
||||
>
|
||||
<Plus className="h-4 w-4 text-gray-400" aria-hidden="true" />
|
||||
<span>Create cycle</span>
|
||||
</button>
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
</>
|
||||
);
|
||||
};
|
@ -502,7 +502,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
<div className="relative h-40 w-80">
|
||||
<ProgressChart
|
||||
distribution={cycleDetails.distribution.completion_chart}
|
||||
distribution={cycleDetails.distribution?.completion_chart ?? {}}
|
||||
startDate={cycleDetails.start_date ?? ""}
|
||||
endDate={cycleDetails.end_date ?? ""}
|
||||
totalIssues={cycleDetails.total_issues}
|
||||
@ -512,7 +512,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{cycleDetails.total_issues > 0 && (
|
||||
{cycleDetails.total_issues > 0 && cycleDetails.distribution && (
|
||||
<div className="h-full w-full pt-5 border-t border-custom-border-200">
|
||||
<SidebarProgressStats
|
||||
distribution={cycleDetails.distribution}
|
||||
|
@ -103,7 +103,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
[issueFilterStore, projectId, workspaceSlug]
|
||||
);
|
||||
|
||||
const cyclesList = projectId ? cycleStore.cycles[projectId.toString()] : undefined;
|
||||
const cyclesList = cycleStore.projectCycles;
|
||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
||||
|
||||
return (
|
||||
|
@ -34,7 +34,7 @@ export const IssueCycleSelect: React.FC<IssueCycleSelectProps> = observer((props
|
||||
if (workspaceSlug && projectId) cycleStore.fetchCycles(workspaceSlug, projectId, "all");
|
||||
};
|
||||
|
||||
const cycles = projectId ? cycleStore.cycles[projectId] : undefined;
|
||||
const cycles = cycleStore.projectCycles;
|
||||
|
||||
const selectedCycle = cycles ? cycles?.find((i) => i.id === value) : undefined;
|
||||
|
||||
|
@ -2,20 +2,18 @@ import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { PencilIcon, StarIcon, TrashIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// icons
|
||||
import { PencilIcon, Sparkles, StarIcon, TrashIcon } from "lucide-react";
|
||||
// types
|
||||
import { IProjectView } from "types";
|
||||
import { CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { calculateTotalFilters } from "helpers/filter.helper";
|
||||
// types
|
||||
import { IProjectView } from "types";
|
||||
|
||||
type Props = {
|
||||
view: IProjectView;
|
||||
@ -64,7 +62,7 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="grid place-items-center h-10 w-10 rounded bg-custom-background-90 group-hover:bg-custom-background-100">
|
||||
<Sparkles size={14} strokeWidth={2} />
|
||||
<PhotoFilterIcon className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="truncate text-sm leading-4 font-medium">{truncateText(view.name, 75)}</p>
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
// components
|
||||
import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "components/workspace";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// icons
|
||||
import { Pencil, Sparkles, Trash2 } from "lucide-react";
|
||||
import { CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { calculateTotalFilters } from "helpers/filter.helper";
|
||||
@ -38,7 +36,7 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="grid place-items-center h-10 w-10 rounded bg-custom-background-90 group-hover:bg-custom-background-100">
|
||||
<Sparkles size={14} strokeWidth={2} />
|
||||
<PhotoFilterIcon className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="truncate text-sm leading-4 font-medium">{truncateText(view.name, 75)}</p>
|
||||
|
@ -133,18 +133,13 @@ export const formatDateDistance = (date: string | Date) => {
|
||||
export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => {
|
||||
if (!startDate || !endDate) return "draft";
|
||||
|
||||
const today = renderDateFormat(new Date());
|
||||
const now = new Date(today);
|
||||
const now = new Date();
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
|
||||
if (start <= now && end >= now) {
|
||||
return "current";
|
||||
} else if (start > now) {
|
||||
return "upcoming";
|
||||
} else {
|
||||
return "completed";
|
||||
}
|
||||
if (start <= now && end >= now) return "current";
|
||||
else if (start > now) return "upcoming";
|
||||
else return "completed";
|
||||
};
|
||||
|
||||
export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Fragment, useCallback, useEffect, useState, ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
@ -31,16 +30,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, peekCycle } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
peekCycle: string;
|
||||
};
|
||||
// fetching project details
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
|
||||
workspaceSlug && projectId ? () => projectStore.fetchProjectDetails(workspaceSlug, projectId) : null
|
||||
);
|
||||
const { workspaceSlug, projectId, peekCycle } = router.query;
|
||||
|
||||
const handleCurrentLayout = useCallback(
|
||||
(_layout: TCycleLayout) => {
|
||||
@ -83,11 +73,13 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
const cycleView = cycleStore?.cycleView;
|
||||
const cycleLayout = cycleStore?.cycleLayout;
|
||||
|
||||
if (!workspaceSlug || !projectId) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CycleCreateUpdateModal
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
isOpen={createModal}
|
||||
handleClose={() => setCreateModal(false)}
|
||||
/>
|
||||
@ -163,29 +155,29 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
|
||||
<Tab.Panels as={Fragment}>
|
||||
<Tab.Panel as="div" className="h-full overflow-y-auto">
|
||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
||||
{cycleView && cycleLayout && (
|
||||
<CyclesView
|
||||
filter={"all"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
peekCycle={peekCycle}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
peekCycle={peekCycle?.toString()}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel as="div" className="p-4 sm:p-5 space-y-5 h-full overflow-y-auto">
|
||||
<ActiveCycleDetails workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
<ActiveCycleDetails workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel as="div" className="h-full overflow-y-auto">
|
||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
||||
{cycleView && cycleLayout && (
|
||||
<CyclesView
|
||||
filter={"upcoming"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
peekCycle={peekCycle}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
peekCycle={peekCycle?.toString()}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
@ -195,9 +187,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
<CyclesView
|
||||
filter={"completed"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
peekCycle={peekCycle}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
peekCycle={peekCycle?.toString()}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
@ -207,9 +199,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
||||
<CyclesView
|
||||
filter={"draft"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
peekCycle={peekCycle}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
peekCycle={peekCycle?.toString()}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
@ -71,23 +71,6 @@ export class CycleService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async updateCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
cycleId: string,
|
||||
data: any,
|
||||
user: IUser | undefined
|
||||
): Promise<any> {
|
||||
return this.put(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data)
|
||||
.then((response) => {
|
||||
trackEventService.trackCycleEvent(response?.data, "CYCLE_UPDATE", user as IUser);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async patchCycle(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -97,7 +80,7 @@ export class CycleService extends APIService {
|
||||
): Promise<any> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data)
|
||||
.then((response) => {
|
||||
trackEventService.trackCycleEvent(response?.data, "CYCLE_UPDATE", user as IUser);
|
||||
if (user) trackEventService.trackCycleEvent(response?.data, "CYCLE_UPDATE", user);
|
||||
return response?.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -133,7 +133,9 @@ export class CycleIssueFilterStore implements ICycleIssueFilterStore {
|
||||
},
|
||||
};
|
||||
|
||||
await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, payload, undefined);
|
||||
const user = this.rootStore.user.currentUser ?? undefined;
|
||||
|
||||
await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, payload, user);
|
||||
} catch (error) {
|
||||
this.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { RootStore } from "../root";
|
||||
import { ProjectService } from "services/project";
|
||||
import { IssueService } from "services/issue";
|
||||
import { CycleService } from "services/cycle.service";
|
||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||
|
||||
export interface ICycleStore {
|
||||
loader: boolean;
|
||||
@ -17,17 +18,23 @@ export interface ICycleStore {
|
||||
|
||||
cycleId: string | null;
|
||||
cycles: {
|
||||
[project_id: string]: ICycle[];
|
||||
[projectId: string]: {
|
||||
[filterType: string]: ICycle[];
|
||||
};
|
||||
};
|
||||
cycle_details: {
|
||||
[cycle_id: string]: ICycle;
|
||||
[cycleId: string]: ICycle;
|
||||
};
|
||||
active_cycle_issues: {
|
||||
[cycle_id: string]: IIssue[];
|
||||
[cycleId: string]: IIssue[];
|
||||
};
|
||||
|
||||
// computed
|
||||
getCycleById: (cycleId: string) => ICycle | null;
|
||||
projectCycles: ICycle[] | null;
|
||||
projectCompletedCycles: ICycle[] | null;
|
||||
projectUpcomingCycles: ICycle[] | null;
|
||||
projectDraftCycles: ICycle[] | null;
|
||||
|
||||
// actions
|
||||
setCycleView: (_cycleView: TCycleView) => void;
|
||||
@ -44,12 +51,12 @@ export interface ICycleStore {
|
||||
fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
|
||||
fetchActiveCycleIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||
|
||||
createCycle: (workspaceSlug: string, projectId: string, data: any) => Promise<ICycle>;
|
||||
updateCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: any) => Promise<ICycle>;
|
||||
createCycle: (workspaceSlug: string, projectId: string, data: Partial<ICycle>) => Promise<ICycle>;
|
||||
patchCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: Partial<ICycle>) => Promise<ICycle>;
|
||||
removeCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||
|
||||
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycle: ICycle) => Promise<any>;
|
||||
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycle: ICycle) => Promise<void>;
|
||||
}
|
||||
|
||||
export class CycleStore implements ICycleStore {
|
||||
@ -61,15 +68,17 @@ export class CycleStore implements ICycleStore {
|
||||
|
||||
cycleId: string | null = null;
|
||||
cycles: {
|
||||
[project_id: string]: ICycle[];
|
||||
[projectId: string]: {
|
||||
[filterType: string]: ICycle[];
|
||||
};
|
||||
} = {};
|
||||
|
||||
cycle_details: {
|
||||
[cycle_id: string]: ICycle;
|
||||
[cycleId: string]: ICycle;
|
||||
} = {};
|
||||
|
||||
active_cycle_issues: {
|
||||
[cycle_id: string]: IIssue[];
|
||||
[cycleId: string]: IIssue[];
|
||||
} = {};
|
||||
|
||||
// root store
|
||||
@ -94,6 +103,9 @@ export class CycleStore implements ICycleStore {
|
||||
|
||||
// computed
|
||||
projectCycles: computed,
|
||||
projectCompletedCycles: computed,
|
||||
projectUpcomingCycles: computed,
|
||||
projectDraftCycles: computed,
|
||||
|
||||
// actions
|
||||
setCycleView: action,
|
||||
@ -107,7 +119,6 @@ export class CycleStore implements ICycleStore {
|
||||
fetchActiveCycleIssues: action,
|
||||
|
||||
createCycle: action,
|
||||
updateCycle: action,
|
||||
removeCycle: action,
|
||||
|
||||
addCycleToFavorites: action,
|
||||
@ -122,8 +133,34 @@ export class CycleStore implements ICycleStore {
|
||||
|
||||
// computed
|
||||
get projectCycles() {
|
||||
if (!this.rootStore.project.projectId) return null;
|
||||
return this.cycles[this.rootStore.project.projectId] || null;
|
||||
const projectId = this.rootStore.project.projectId;
|
||||
|
||||
if (!projectId) return null;
|
||||
return this.cycles[projectId]?.all || null;
|
||||
}
|
||||
|
||||
get projectCompletedCycles() {
|
||||
const projectId = this.rootStore.project.projectId;
|
||||
|
||||
if (!projectId) return null;
|
||||
|
||||
return this.cycles[projectId]?.completed || null;
|
||||
}
|
||||
|
||||
get projectUpcomingCycles() {
|
||||
const projectId = this.rootStore.project.projectId;
|
||||
|
||||
if (!projectId) return null;
|
||||
|
||||
return this.cycles[projectId]?.upcoming || null;
|
||||
}
|
||||
|
||||
get projectDraftCycles() {
|
||||
const projectId = this.rootStore.project.projectId;
|
||||
|
||||
if (!projectId) return null;
|
||||
|
||||
return this.cycles[projectId]?.draft || null;
|
||||
}
|
||||
|
||||
getCycleById = (cycleId: string) => this.cycle_details[cycleId] || null;
|
||||
@ -159,7 +196,7 @@ export class CycleStore implements ICycleStore {
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: cyclesResponse,
|
||||
[projectId]: { ...this.cycles[projectId], [params]: cyclesResponse },
|
||||
};
|
||||
this.loader = false;
|
||||
this.error = null;
|
||||
@ -209,16 +246,14 @@ export class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
createCycle = async (workspaceSlug: string, projectId: string, data: any) => {
|
||||
createCycle = async (workspaceSlug: string, projectId: string, data: Partial<ICycle>) => {
|
||||
try {
|
||||
console.log("Cycle Creating");
|
||||
const response = await this.cycleService.createCycle(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
data,
|
||||
this.rootStore.user.currentUser
|
||||
);
|
||||
console.log("Cycle created");
|
||||
|
||||
runInAction(() => {
|
||||
this.cycle_details = {
|
||||
@ -237,30 +272,7 @@ export class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
updateCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: any) => {
|
||||
try {
|
||||
const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, data, undefined);
|
||||
|
||||
const _cycleDetails = {
|
||||
...this.cycle_details,
|
||||
[cycleId]: { ...this.cycle_details[cycleId], ...response },
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.cycle_details = _cycleDetails;
|
||||
});
|
||||
|
||||
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
|
||||
this.fetchCycles(workspaceSlug, projectId, _currentView);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to update cycle from cycle store");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
patchCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: any) => {
|
||||
patchCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: Partial<ICycle>) => {
|
||||
try {
|
||||
const _response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data, undefined);
|
||||
|
||||
@ -297,30 +309,60 @@ export class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => {
|
||||
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
||||
|
||||
const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? [];
|
||||
const allCyclesList = this.projectCycles ?? [];
|
||||
|
||||
try {
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: true };
|
||||
return cycle;
|
||||
[projectId]: {
|
||||
...this.cycles[projectId],
|
||||
[cycleStatus]: statusCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: true };
|
||||
return c;
|
||||
}),
|
||||
all: allCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: true };
|
||||
return c;
|
||||
}),
|
||||
},
|
||||
};
|
||||
this.cycle_details = {
|
||||
...this.cycle_details,
|
||||
[cycle.id]: { ...this.cycle_details[cycle.id], is_favorite: true },
|
||||
};
|
||||
});
|
||||
|
||||
// updating through api.
|
||||
const response = await this.cycleService.addCycleToFavorites(workspaceSlug, projectId, { cycle: cycleId });
|
||||
const response = await this.cycleService.addCycleToFavorites(workspaceSlug, projectId, { cycle: cycle.id });
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to add cycle to favorites in the cycles store", error);
|
||||
// resetting the local state
|
||||
|
||||
// reset on error
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: false };
|
||||
return cycle;
|
||||
[projectId]: {
|
||||
...this.cycles[projectId],
|
||||
[cycleStatus]: statusCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: false };
|
||||
return c;
|
||||
}),
|
||||
all: allCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: false };
|
||||
return c;
|
||||
}),
|
||||
},
|
||||
};
|
||||
this.cycle_details = {
|
||||
...this.cycle_details,
|
||||
[cycle.id]: { ...this.cycle_details[cycle.id], is_favorite: false },
|
||||
};
|
||||
});
|
||||
|
||||
@ -328,30 +370,62 @@ export class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
removeCycleFromFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
removeCycleFromFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => {
|
||||
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
||||
|
||||
const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? [];
|
||||
const allCyclesList = this.projectCycles ?? [];
|
||||
|
||||
try {
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: false };
|
||||
return cycle;
|
||||
[projectId]: {
|
||||
...this.cycles[projectId],
|
||||
[cycleStatus]: statusCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: false };
|
||||
return c;
|
||||
}),
|
||||
all: allCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: false };
|
||||
return c;
|
||||
}),
|
||||
},
|
||||
};
|
||||
this.cycle_details = {
|
||||
...this.cycle_details,
|
||||
[cycle.id]: { ...this.cycle_details[cycle.id], is_favorite: false },
|
||||
};
|
||||
});
|
||||
const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycleId);
|
||||
|
||||
const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycle.id);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to remove cycle from favorites - Cycle Store", error);
|
||||
|
||||
// reset on error
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: true };
|
||||
return cycle;
|
||||
[projectId]: {
|
||||
...this.cycles[projectId],
|
||||
[cycleStatus]: statusCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: true };
|
||||
return c;
|
||||
}),
|
||||
all: allCyclesList?.map((c) => {
|
||||
if (c.id === cycle.id) return { ...c, is_favorite: true };
|
||||
return c;
|
||||
}),
|
||||
},
|
||||
};
|
||||
this.cycle_details = {
|
||||
...this.cycle_details,
|
||||
[cycle.id]: { ...this.cycle_details[cycle.id], is_favorite: true },
|
||||
};
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
13
web/types/cycles.d.ts
vendored
13
web/types/cycles.d.ts
vendored
@ -1,13 +1,4 @@
|
||||
import type {
|
||||
IUser,
|
||||
IIssue,
|
||||
IProject,
|
||||
IProjectLite,
|
||||
IWorkspace,
|
||||
IWorkspaceLite,
|
||||
IIssueFilterOptions,
|
||||
IUserLite,
|
||||
} from "types";
|
||||
import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "types";
|
||||
|
||||
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
|
||||
|
||||
@ -20,7 +11,7 @@ export interface ICycle {
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
description: string;
|
||||
distribution: {
|
||||
distribution?: {
|
||||
assignees: TAssigneesDistribution[];
|
||||
completion_chart: TCompletionChartDistribution;
|
||||
labels: TLabelsDistribution[];
|
||||
|
Loading…
Reference in New Issue
Block a user