forked from github/plane
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 { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { isLoading } = useSWR(
|
useSWR(
|
||||||
workspaceSlug && projectId ? `ACTIVE_CYCLE_ISSUE_${projectId}_CURRENT` : null,
|
workspaceSlug && projectId ? `ACTIVE_CYCLE_ISSUE_${projectId}_CURRENT` : null,
|
||||||
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, 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 cycle = activeCycle ? activeCycle[0] : null;
|
||||||
const issues = (cycleStore?.active_cycle_issues as any) || null;
|
const issues = (cycleStore?.active_cycle_issues as any) || null;
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
// : null
|
// : null
|
||||||
// ) as { data: IIssue[] | undefined };
|
// ) as { data: IIssue[] | undefined };
|
||||||
|
|
||||||
if (isLoading)
|
if (!cycle)
|
||||||
return (
|
return (
|
||||||
<Loader>
|
<Loader>
|
||||||
<Loader.Item height="250px" />
|
<Loader.Item height="250px" />
|
||||||
@ -143,7 +143,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -156,7 +156,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
@ -87,7 +87,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -100,7 +100,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
@ -12,7 +12,7 @@ export interface ICyclesBoard {
|
|||||||
filter: string;
|
filter: string;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
peekCycle: string;
|
peekCycle: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
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();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -100,7 +100,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
@ -15,7 +15,7 @@ export interface ICyclesView {
|
|||||||
layout: TCycleLayout;
|
layout: TCycleLayout;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
peekCycle: string;
|
peekCycle: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CyclesView: FC<ICyclesView> = observer((props) => {
|
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
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -5,7 +5,6 @@ export * from "./gantt-chart";
|
|||||||
export * from "./cycles-view";
|
export * from "./cycles-view";
|
||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./modal";
|
export * from "./modal";
|
||||||
export * from "./select";
|
|
||||||
export * from "./sidebar";
|
export * from "./sidebar";
|
||||||
export * from "./transfer-issues-modal";
|
export * from "./transfer-issues-modal";
|
||||||
export * from "./transfer-issues";
|
export * from "./transfer-issues";
|
||||||
@ -13,7 +12,6 @@ export * from "./cycles-list";
|
|||||||
export * from "./cycles-list-item";
|
export * from "./cycles-list-item";
|
||||||
export * from "./cycles-board";
|
export * from "./cycles-board";
|
||||||
export * from "./cycles-board-card";
|
export * from "./cycles-board-card";
|
||||||
export * from "./cycles-gantt";
|
|
||||||
export * from "./delete-modal";
|
export * from "./delete-modal";
|
||||||
export * from "./cycle-peek-overview";
|
export * from "./cycle-peek-overview";
|
||||||
export * from "./cycles-list-item";
|
export * from "./cycles-list-item";
|
||||||
|
@ -50,7 +50,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||||||
|
|
||||||
const updateCycle = async (cycleId: string, payload: Partial<ICycle>) =>
|
const updateCycle = async (cycleId: string, payload: Partial<ICycle>) =>
|
||||||
cycleStore
|
cycleStore
|
||||||
.updateCycle(workspaceSlug, projectId, cycleId, payload)
|
.patchCycle(workspaceSlug, projectId, cycleId, payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
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>
|
||||||
<div className="relative h-40 w-80">
|
<div className="relative h-40 w-80">
|
||||||
<ProgressChart
|
<ProgressChart
|
||||||
distribution={cycleDetails.distribution.completion_chart}
|
distribution={cycleDetails.distribution?.completion_chart ?? {}}
|
||||||
startDate={cycleDetails.start_date ?? ""}
|
startDate={cycleDetails.start_date ?? ""}
|
||||||
endDate={cycleDetails.end_date ?? ""}
|
endDate={cycleDetails.end_date ?? ""}
|
||||||
totalIssues={cycleDetails.total_issues}
|
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">
|
<div className="h-full w-full pt-5 border-t border-custom-border-200">
|
||||||
<SidebarProgressStats
|
<SidebarProgressStats
|
||||||
distribution={cycleDetails.distribution}
|
distribution={cycleDetails.distribution}
|
||||||
|
@ -103,7 +103,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
[issueFilterStore, projectId, workspaceSlug]
|
[issueFilterStore, projectId, workspaceSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cyclesList = projectId ? cycleStore.cycles[projectId.toString()] : undefined;
|
const cyclesList = cycleStore.projectCycles;
|
||||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -34,7 +34,7 @@ export const IssueCycleSelect: React.FC<IssueCycleSelectProps> = observer((props
|
|||||||
if (workspaceSlug && projectId) cycleStore.fetchCycles(workspaceSlug, projectId, "all");
|
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;
|
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 Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { PencilIcon, StarIcon, TrashIcon } from "lucide-react";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views";
|
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { PencilIcon, Sparkles, StarIcon, TrashIcon } from "lucide-react";
|
|
||||||
// types
|
|
||||||
import { IProjectView } from "types";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import { calculateTotalFilters } from "helpers/filter.helper";
|
import { calculateTotalFilters } from "helpers/filter.helper";
|
||||||
|
// types
|
||||||
|
import { IProjectView } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
view: IProjectView;
|
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 justify-between w-full">
|
||||||
<div className="flex items-center gap-4">
|
<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">
|
<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>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="truncate text-sm leading-4 font-medium">{truncateText(view.name, 75)}</p>
|
<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 { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useState } from "react";
|
import { Pencil, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "components/workspace";
|
import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "components/workspace";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { Pencil, Sparkles, Trash2 } from "lucide-react";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import { calculateTotalFilters } from "helpers/filter.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 justify-between w-full">
|
||||||
<div className="flex items-center gap-4">
|
<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">
|
<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>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="truncate text-sm leading-4 font-medium">{truncateText(view.name, 75)}</p>
|
<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) => {
|
export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => {
|
||||||
if (!startDate || !endDate) return "draft";
|
if (!startDate || !endDate) return "draft";
|
||||||
|
|
||||||
const today = renderDateFormat(new Date());
|
const now = new Date();
|
||||||
const now = new Date(today);
|
|
||||||
const start = new Date(startDate);
|
const start = new Date(startDate);
|
||||||
const end = new Date(endDate);
|
const end = new Date(endDate);
|
||||||
|
|
||||||
if (start <= now && end >= now) {
|
if (start <= now && end >= now) return "current";
|
||||||
return "current";
|
else if (start > now) return "upcoming";
|
||||||
} else if (start > now) {
|
else return "completed";
|
||||||
return "upcoming";
|
|
||||||
} else {
|
|
||||||
return "completed";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => {
|
export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Fragment, useCallback, useEffect, useState, ReactElement } from "react";
|
import { Fragment, useCallback, useEffect, useState, ReactElement } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { Tab } from "@headlessui/react";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
@ -31,16 +30,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
const { currentProjectDetails } = projectStore;
|
const { currentProjectDetails } = projectStore;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, peekCycle } = router.query as {
|
const { workspaceSlug, projectId, peekCycle } = router.query;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
peekCycle: string;
|
|
||||||
};
|
|
||||||
// fetching project details
|
|
||||||
useSWR(
|
|
||||||
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
|
|
||||||
workspaceSlug && projectId ? () => projectStore.fetchProjectDetails(workspaceSlug, projectId) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCurrentLayout = useCallback(
|
const handleCurrentLayout = useCallback(
|
||||||
(_layout: TCycleLayout) => {
|
(_layout: TCycleLayout) => {
|
||||||
@ -83,11 +73,13 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
const cycleView = cycleStore?.cycleView;
|
const cycleView = cycleStore?.cycleView;
|
||||||
const cycleLayout = cycleStore?.cycleLayout;
|
const cycleLayout = cycleStore?.cycleLayout;
|
||||||
|
|
||||||
|
if (!workspaceSlug || !projectId) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CycleCreateUpdateModal
|
<CycleCreateUpdateModal
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId}
|
projectId={projectId.toString()}
|
||||||
isOpen={createModal}
|
isOpen={createModal}
|
||||||
handleClose={() => setCreateModal(false)}
|
handleClose={() => setCreateModal(false)}
|
||||||
/>
|
/>
|
||||||
@ -163,29 +155,29 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
<Tab.Panels as={Fragment}>
|
<Tab.Panels as={Fragment}>
|
||||||
<Tab.Panel as="div" className="h-full overflow-y-auto">
|
<Tab.Panel as="div" className="h-full overflow-y-auto">
|
||||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
{cycleView && cycleLayout && (
|
||||||
<CyclesView
|
<CyclesView
|
||||||
filter={"all"}
|
filter={"all"}
|
||||||
layout={cycleLayout as TCycleLayout}
|
layout={cycleLayout as TCycleLayout}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId}
|
projectId={projectId.toString()}
|
||||||
peekCycle={peekCycle}
|
peekCycle={peekCycle?.toString()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
|
|
||||||
<Tab.Panel as="div" className="p-4 sm:p-5 space-y-5 h-full overflow-y-auto">
|
<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>
|
||||||
|
|
||||||
<Tab.Panel as="div" className="h-full overflow-y-auto">
|
<Tab.Panel as="div" className="h-full overflow-y-auto">
|
||||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
{cycleView && cycleLayout && (
|
||||||
<CyclesView
|
<CyclesView
|
||||||
filter={"upcoming"}
|
filter={"upcoming"}
|
||||||
layout={cycleLayout as TCycleLayout}
|
layout={cycleLayout as TCycleLayout}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId}
|
projectId={projectId.toString()}
|
||||||
peekCycle={peekCycle}
|
peekCycle={peekCycle?.toString()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
@ -195,9 +187,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
<CyclesView
|
<CyclesView
|
||||||
filter={"completed"}
|
filter={"completed"}
|
||||||
layout={cycleLayout as TCycleLayout}
|
layout={cycleLayout as TCycleLayout}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId}
|
projectId={projectId.toString()}
|
||||||
peekCycle={peekCycle}
|
peekCycle={peekCycle?.toString()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
@ -207,9 +199,9 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
|
|||||||
<CyclesView
|
<CyclesView
|
||||||
filter={"draft"}
|
filter={"draft"}
|
||||||
layout={cycleLayout as TCycleLayout}
|
layout={cycleLayout as TCycleLayout}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId}
|
projectId={projectId.toString()}
|
||||||
peekCycle={peekCycle}
|
peekCycle={peekCycle?.toString()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tab.Panel>
|
</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(
|
async patchCycle(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -97,7 +80,7 @@ export class CycleService extends APIService {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data)
|
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}/`, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
trackEventService.trackCycleEvent(response?.data, "CYCLE_UPDATE", user as IUser);
|
if (user) trackEventService.trackCycleEvent(response?.data, "CYCLE_UPDATE", user);
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.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) {
|
} catch (error) {
|
||||||
this.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
this.fetchCycleFilters(workspaceSlug, projectId, cycleId);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { RootStore } from "../root";
|
|||||||
import { ProjectService } from "services/project";
|
import { ProjectService } from "services/project";
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
import { CycleService } from "services/cycle.service";
|
import { CycleService } from "services/cycle.service";
|
||||||
|
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||||
|
|
||||||
export interface ICycleStore {
|
export interface ICycleStore {
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
@ -17,17 +18,23 @@ export interface ICycleStore {
|
|||||||
|
|
||||||
cycleId: string | null;
|
cycleId: string | null;
|
||||||
cycles: {
|
cycles: {
|
||||||
[project_id: string]: ICycle[];
|
[projectId: string]: {
|
||||||
|
[filterType: string]: ICycle[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
cycle_details: {
|
cycle_details: {
|
||||||
[cycle_id: string]: ICycle;
|
[cycleId: string]: ICycle;
|
||||||
};
|
};
|
||||||
active_cycle_issues: {
|
active_cycle_issues: {
|
||||||
[cycle_id: string]: IIssue[];
|
[cycleId: string]: IIssue[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
getCycleById: (cycleId: string) => ICycle | null;
|
getCycleById: (cycleId: string) => ICycle | null;
|
||||||
|
projectCycles: ICycle[] | null;
|
||||||
|
projectCompletedCycles: ICycle[] | null;
|
||||||
|
projectUpcomingCycles: ICycle[] | null;
|
||||||
|
projectDraftCycles: ICycle[] | null;
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
setCycleView: (_cycleView: TCycleView) => void;
|
setCycleView: (_cycleView: TCycleView) => void;
|
||||||
@ -44,12 +51,12 @@ export interface ICycleStore {
|
|||||||
fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
|
fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
|
||||||
fetchActiveCycleIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
fetchActiveCycleIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||||
|
|
||||||
createCycle: (workspaceSlug: string, projectId: string, data: any) => Promise<ICycle>;
|
createCycle: (workspaceSlug: string, projectId: string, data: Partial<ICycle>) => Promise<ICycle>;
|
||||||
updateCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: any) => Promise<ICycle>;
|
patchCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: Partial<ICycle>) => Promise<ICycle>;
|
||||||
removeCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
removeCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||||
|
|
||||||
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycle: ICycle) => Promise<any>;
|
||||||
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycle: ICycle) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CycleStore implements ICycleStore {
|
export class CycleStore implements ICycleStore {
|
||||||
@ -61,15 +68,17 @@ export class CycleStore implements ICycleStore {
|
|||||||
|
|
||||||
cycleId: string | null = null;
|
cycleId: string | null = null;
|
||||||
cycles: {
|
cycles: {
|
||||||
[project_id: string]: ICycle[];
|
[projectId: string]: {
|
||||||
|
[filterType: string]: ICycle[];
|
||||||
|
};
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
cycle_details: {
|
cycle_details: {
|
||||||
[cycle_id: string]: ICycle;
|
[cycleId: string]: ICycle;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
active_cycle_issues: {
|
active_cycle_issues: {
|
||||||
[cycle_id: string]: IIssue[];
|
[cycleId: string]: IIssue[];
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
// root store
|
// root store
|
||||||
@ -94,6 +103,9 @@ export class CycleStore implements ICycleStore {
|
|||||||
|
|
||||||
// computed
|
// computed
|
||||||
projectCycles: computed,
|
projectCycles: computed,
|
||||||
|
projectCompletedCycles: computed,
|
||||||
|
projectUpcomingCycles: computed,
|
||||||
|
projectDraftCycles: computed,
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
setCycleView: action,
|
setCycleView: action,
|
||||||
@ -107,7 +119,6 @@ export class CycleStore implements ICycleStore {
|
|||||||
fetchActiveCycleIssues: action,
|
fetchActiveCycleIssues: action,
|
||||||
|
|
||||||
createCycle: action,
|
createCycle: action,
|
||||||
updateCycle: action,
|
|
||||||
removeCycle: action,
|
removeCycle: action,
|
||||||
|
|
||||||
addCycleToFavorites: action,
|
addCycleToFavorites: action,
|
||||||
@ -122,8 +133,34 @@ export class CycleStore implements ICycleStore {
|
|||||||
|
|
||||||
// computed
|
// computed
|
||||||
get projectCycles() {
|
get projectCycles() {
|
||||||
if (!this.rootStore.project.projectId) return null;
|
const projectId = this.rootStore.project.projectId;
|
||||||
return this.cycles[this.rootStore.project.projectId] || null;
|
|
||||||
|
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;
|
getCycleById = (cycleId: string) => this.cycle_details[cycleId] || null;
|
||||||
@ -159,7 +196,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycles = {
|
this.cycles = {
|
||||||
...this.cycles,
|
...this.cycles,
|
||||||
[projectId]: cyclesResponse,
|
[projectId]: { ...this.cycles[projectId], [params]: cyclesResponse },
|
||||||
};
|
};
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
this.error = null;
|
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 {
|
try {
|
||||||
console.log("Cycle Creating");
|
|
||||||
const response = await this.cycleService.createCycle(
|
const response = await this.cycleService.createCycle(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
data,
|
data,
|
||||||
this.rootStore.user.currentUser
|
this.rootStore.user.currentUser
|
||||||
);
|
);
|
||||||
console.log("Cycle created");
|
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycle_details = {
|
this.cycle_details = {
|
||||||
@ -237,30 +272,7 @@ export class CycleStore implements ICycleStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateCycle = 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.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) => {
|
|
||||||
try {
|
try {
|
||||||
const _response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data, undefined);
|
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 {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycles = {
|
this.cycles = {
|
||||||
...this.cycles,
|
...this.cycles,
|
||||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
[projectId]: {
|
||||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: true };
|
...this.cycles[projectId],
|
||||||
return cycle;
|
[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.
|
// 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;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to add cycle to favorites in the cycles store", error);
|
console.log("Failed to add cycle to favorites in the cycles store", error);
|
||||||
// resetting the local state
|
|
||||||
|
// reset on error
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycles = {
|
this.cycles = {
|
||||||
...this.cycles,
|
...this.cycles,
|
||||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
[projectId]: {
|
||||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: false };
|
...this.cycles[projectId],
|
||||||
return cycle;
|
[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 {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycles = {
|
this.cycles = {
|
||||||
...this.cycles,
|
...this.cycles,
|
||||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
[projectId]: {
|
||||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: false };
|
...this.cycles[projectId],
|
||||||
return cycle;
|
[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;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to remove cycle from favorites - Cycle Store", error);
|
console.log("Failed to remove cycle from favorites - Cycle Store", error);
|
||||||
|
|
||||||
|
// reset on error
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycles = {
|
this.cycles = {
|
||||||
...this.cycles,
|
...this.cycles,
|
||||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
[projectId]: {
|
||||||
if (cycle.id === cycleId) return { ...cycle, is_favorite: true };
|
...this.cycles[projectId],
|
||||||
return cycle;
|
[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;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
13
web/types/cycles.d.ts
vendored
13
web/types/cycles.d.ts
vendored
@ -1,13 +1,4 @@
|
|||||||
import type {
|
import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "types";
|
||||||
IUser,
|
|
||||||
IIssue,
|
|
||||||
IProject,
|
|
||||||
IProjectLite,
|
|
||||||
IWorkspace,
|
|
||||||
IWorkspaceLite,
|
|
||||||
IIssueFilterOptions,
|
|
||||||
IUserLite,
|
|
||||||
} from "types";
|
|
||||||
|
|
||||||
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
|
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
|
||||||
|
|
||||||
@ -20,7 +11,7 @@ export interface ICycle {
|
|||||||
created_at: Date;
|
created_at: Date;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
description: string;
|
description: string;
|
||||||
distribution: {
|
distribution?: {
|
||||||
assignees: TAssigneesDistribution[];
|
assignees: TAssigneesDistribution[];
|
||||||
completion_chart: TCompletionChartDistribution;
|
completion_chart: TCompletionChartDistribution;
|
||||||
labels: TLabelsDistribution[];
|
labels: TLabelsDistribution[];
|
||||||
|
Loading…
Reference in New Issue
Block a user