chore: initialize and implement project estimate store

This commit is contained in:
Aaryan Khandelwal 2023-12-18 16:26:17 +05:30
parent 34e4b6e2ff
commit 73e36cef38
16 changed files with 370 additions and 180 deletions

View File

@ -1,10 +1,8 @@
import { useRouter } from "next/router";
import { useEffect } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useLabel } from "hooks/store";
// hook
import useEstimateOption from "hooks/use-estimate-option";
// store hooks
import { useEstimate, useLabel } from "hooks/store";
// icons
import { Tooltip, BlockedIcon, BlockerIcon, RelatedIcon, LayersIcon, DiceIcon } from "@plane/ui";
import {
@ -76,7 +74,7 @@ const UserLink = ({ activity }: { activity: IIssueActivity }) => {
const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) => {
// store hooks
const {
workspaceLabel: { workspaceLabels, fetchWorkspaceLabels },
workspace: { workspaceLabels, fetchWorkspaceLabels },
} = useLabel();
useEffect(() => {
@ -94,16 +92,21 @@ const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; works
);
});
const EstimatePoint = ({ point }: { point: string }) => {
const { estimateValue, isEstimateActive } = useEstimateOption(Number(point));
const EstimatePoint = observer((props: { point: string }) => {
const { point } = props;
const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
const currentPoint = Number(point) + 1;
const estimateValue = getEstimatePointValue(Number(point));
return (
<span className="font-medium text-custom-text-100">
{isEstimateActive ? estimateValue : `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`}
{areEstimatesEnabledForCurrentProject
? estimateValue
: `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`}
</span>
);
};
});
const activityDetails: {
[key: string]: {

View File

@ -2,11 +2,9 @@ import React, { useEffect } from "react";
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
// store hooks
import { useEstimate } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { Button, Input, TextArea } from "@plane/ui";
@ -36,16 +34,14 @@ type FormValues = typeof defaultValues;
export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
const { handleClose, data, isOpen } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const {
projectEstimates: { createEstimate, updateEstimate },
} = useMobxStore();
// store hooks
const { createEstimate, updateEstimate } = useEstimate();
// form info
// toast alert
const { setToastAlert } = useToast();
const {
formState: { errors, isSubmitting },
handleSubmit,
@ -60,8 +56,6 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
reset();
};
const { setToastAlert } = useToast();
const handleCreateEstimate = async (payload: IEstimateFormData) => {
if (!workspaceSlug || !projectId) return;
@ -299,8 +293,8 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
? "Updating Estimate..."
: "Update Estimate"
: isSubmitting
? "Creating Estimate..."
: "Create Estimate"}
? "Creating Estimate..."
: "Create Estimate"}
</Button>
</div>
</form>

View File

@ -1,15 +1,13 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
// store
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import { AlertTriangle } from "lucide-react";
// store hooks
import { useEstimate } from "hooks/store";
import useToast from "hooks/use-toast";
// types
import { IEstimate } from "types";
// icons
import { AlertTriangle } from "lucide-react";
// ui
import { Button } from "@plane/ui";
@ -21,18 +19,14 @@ type Props = {
export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, data } = props;
// states
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { projectEstimates: projectEstimatesStore } = useMobxStore();
// states
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
// hooks
// store hooks
const { deleteEstimate } = useEstimate();
// toast alert
const { setToastAlert } = useToast();
const handleEstimateDelete = () => {
@ -40,8 +34,7 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
const estimateId = data?.id!;
projectEstimatesStore
.deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId)
deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId)
.then(() => {
setIsDeleteLoading(false);
handleClose();

View File

@ -1,40 +1,36 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
// store
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react";
// store hooks
import { useEstimate } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// components
import { CreateUpdateEstimateModal, DeleteEstimateModal, EstimateListItem } from "components/estimates";
//hooks
import useToast from "hooks/use-toast";
// ui
import { Button, Loader } from "@plane/ui";
import { EmptyState } from "components/common";
// icons
import { Plus } from "lucide-react";
// images
import emptyEstimate from "public/empty-state/estimate.svg";
// types
import { IEstimate } from "types";
export const EstimatesList: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const {
project: { currentProjectDetails, updateProject },
projectEstimates: { projectEstimates, getProjectEstimateById },
} = useMobxStore();
// states
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
const [estimateToDelete, setEstimateToDelete] = useState<string | null>(null);
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
// hooks
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const {
project: { currentProjectDetails, updateProject },
} = useMobxStore();
const { projectEstimates, getProjectEstimateById } = useEstimate();
// toast alert
const { setToastAlert } = useToast();
// derived values
const estimatesList = projectEstimates;
const editEstimate = (estimate: IEstimate) => {
setEstimateFormOpen(true);
@ -96,10 +92,10 @@ export const EstimatesList: React.FC = observer(() => {
</div>
</section>
{estimatesList ? (
estimatesList.length > 0 ? (
{projectEstimates ? (
projectEstimates.length > 0 ? (
<section className="h-full overflow-y-auto bg-custom-background-100">
{estimatesList.map((estimate) => (
{projectEstimates.map((estimate) => (
<EstimateListItem
key={estimate.id}
estimate={estimate}

View File

@ -1,21 +1,22 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { Triangle } from "lucide-react";
// store hooks
import { useEstimate } from "hooks/store";
// ui
import { CustomSelect } from "@plane/ui";
// icons
import { Triangle } from "lucide-react";
// fetch-keys
import useEstimateOption from "hooks/use-estimate-option";
type Props = {
value: number | null;
onChange: (value: number | null) => void;
};
export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
const { isEstimateActive, estimatePoints } = useEstimateOption();
export const IssueEstimateSelect: React.FC<Props> = observer((props) => {
const { value, onChange } = props;
if (!isEstimateActive) return null;
const { areEstimatesEnabledForCurrentProject, activeEstimateDetails } = useEstimate();
if (!areEstimatesEnabledForCurrentProject) return null;
return (
<CustomSelect
@ -24,7 +25,7 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
<div className="flex items-center justify-center gap-1 text-xs">
<Triangle className={`h-3 w-3 ${value !== null ? "text-custom-text-200" : "text-custom-text-300"}`} />
<span className={value !== null ? "text-custom-text-200" : "text-custom-text-300"}>
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate"}
{activeEstimateDetails?.points?.find((e) => e.key === value)?.value ?? "Estimate"}
</span>
</div>
}
@ -40,8 +41,8 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
None
</>
</CustomSelect.Option>
{estimatePoints &&
estimatePoints.map((point) => (
{activeEstimateDetails?.points &&
activeEstimateDetails.points?.map((point) => (
<CustomSelect.Option key={point.key} value={point.key}>
<>
<span>
@ -53,4 +54,4 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
))}
</CustomSelect>
);
};
});

View File

@ -1,11 +1,10 @@
import React from "react";
// hooks
import useEstimateOption from "hooks/use-estimate-option";
import { observer } from "mobx-react-lite";
import { Triangle } from "lucide-react";
// store hooks
import { useEstimate } from "hooks/store";
// ui
import { CustomSelect } from "@plane/ui";
// icons
import { Triangle } from "lucide-react";
type Props = {
value: number | null;
@ -13,10 +12,13 @@ type Props = {
disabled?: boolean;
};
export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabled = false }) => {
const { estimatePoints } = useEstimateOption();
export const SidebarEstimateSelect: React.FC<Props> = observer((props) => {
const { value, onChange, disabled = false } = props;
const { activeEstimateDetails, getEstimatePointValue } = useEstimate();
const currentEstimate = getEstimatePointValue(value);
const currentEstimate = estimatePoints?.find((e) => e.key === value)?.value;
return (
<CustomSelect
value={value}
@ -43,8 +45,8 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabl
None
</>
</CustomSelect.Option>
{estimatePoints &&
estimatePoints.map((point) => (
{activeEstimateDetails?.points &&
activeEstimateDetails?.points?.map((point) => (
<CustomSelect.Option key={point.key} value={point.key}>
<>
<span>
@ -56,4 +58,4 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabl
))}
</CustomSelect>
);
};
});

View File

@ -5,11 +5,10 @@ import { mutate } from "swr";
import { Controller, UseFormWatch } from "react-hook-form";
import { Bell, CalendarDays, LinkIcon, Plus, Signal, Tag, Trash2, Triangle, LayoutPanelTop } from "lucide-react";
// hooks
import { useProjectState, useUser } from "hooks/store";
import { useEstimate, useProjectState, useUser } from "hooks/store";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
import useEstimateOption from "hooks/use-estimate-option";
// services
import { IssueService } from "services/issue";
import { ModuleService } from "services/module.service";
@ -89,12 +88,11 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
membership: { currentProjectRole },
} = useUser();
const { projectStates } = useProjectState();
const { areEstimatesEnabledForCurrentProject } = useEstimate();
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
const { isEstimateActive } = useEstimateOption();
const { loading, handleSubscribe, handleUnsubscribe, subscribed } = useUserIssueNotificationSubscription(
workspaceSlug,
projectId,
@ -341,27 +339,28 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
</div>
</div>
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && isEstimateActive && (
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
<Triangle className="h-4 w-4 flex-shrink-0 " />
<p>Estimate</p>
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) &&
areEstimatesEnabledForCurrentProject && (
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
<Triangle className="h-4 w-4 flex-shrink-0 " />
<p>Estimate</p>
</div>
<div className="sm:basis-1/2">
<Controller
control={control}
name="estimate_point"
render={({ field: { value } }) => (
<SidebarEstimateSelect
value={value}
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
disabled={!isAllowed || uneditable}
/>
)}
/>
</div>
</div>
<div className="sm:basis-1/2">
<Controller
control={control}
name="estimate_point"
render={({ field: { value } }) => (
<SidebarEstimateSelect
value={value}
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
disabled={!isAllowed || uneditable}
/>
)}
/>
</div>
</div>
)}
)}
</div>
)}
{showSecondSection && (

View File

@ -1,10 +1,10 @@
import React from "react";
// hooks
import useEstimateOption from "hooks/use-estimate-option";
import { observer } from "mobx-react-lite";
import { Triangle } from "lucide-react";
// store hooks
import { useEstimate } from "hooks/store";
// ui
import { CustomSelect, Tooltip } from "@plane/ui";
// icons
import { Triangle } from "lucide-react";
// types
import { IIssue } from "types";
@ -16,16 +16,11 @@ type Props = {
disabled: boolean;
};
export const ViewEstimateSelect: React.FC<Props> = ({
issue,
onChange,
tooltipPosition = "top",
customButton = false,
disabled,
}) => {
const { isEstimateActive, estimatePoints } = useEstimateOption(issue.estimate_point);
export const ViewEstimateSelect: React.FC<Props> = observer((props) => {
const { issue, onChange, tooltipPosition = "top", customButton = false, disabled } = props;
const { areEstimatesEnabledForCurrentProject, activeEstimateDetails, getEstimatePointValue } = useEstimate();
const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value;
const estimateValue = getEstimatePointValue(issue.estimate_point);
const estimateLabels = (
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue} position={tooltipPosition}>
@ -36,7 +31,7 @@ export const ViewEstimateSelect: React.FC<Props> = ({
</Tooltip>
);
if (!isEstimateActive) return null;
if (!areEstimatesEnabledForCurrentProject) return null;
return (
<CustomSelect
@ -56,7 +51,7 @@ export const ViewEstimateSelect: React.FC<Props> = ({
None
</>
</CustomSelect.Option>
{estimatePoints?.map((estimate) => (
{activeEstimateDetails?.points?.map((estimate) => (
<CustomSelect.Option key={estimate.id} value={estimate.key}>
<>
<Triangle className="h-3 w-3" />
@ -66,4 +61,4 @@ export const ViewEstimateSelect: React.FC<Props> = ({
))}
</CustomSelect>
);
};
});

View File

@ -1,5 +1,7 @@
export * from "./use-application";
export * from "./use-cycle";
export * from "./use-estimate";
export * from "./use-inbox";
export * from "./use-label";
export * from "./use-member";
export * from "./use-module";

View File

@ -0,0 +1,11 @@
import { useContext } from "react";
// mobx store
import { StoreContext } from "contexts/store-context";
// types
import { IProjectEstimateStore } from "store/estimate.store";
export const useEstimate = (): IProjectEstimateStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useCycle must be used within StoreProvider");
return context.estimate;
};

View File

@ -1,45 +0,0 @@
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import { ProjectEstimateService } from "services/project";
// hooks
import useProjectDetails from "hooks/use-project-details";
// helpers
import { orderArrayBy } from "helpers/array.helper";
// fetch-keys
import { ESTIMATE_DETAILS } from "constants/fetch-keys";
// services
const projectEstimateService = new ProjectEstimateService();
const useEstimateOption = (estimateKey?: number | null) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { projectDetails } = useProjectDetails();
const { data: estimateDetails } = useSWR(
workspaceSlug && projectId && projectDetails && projectDetails?.estimate
? ESTIMATE_DETAILS(projectDetails.estimate as string)
: null,
workspaceSlug && projectId && projectDetails && projectDetails.estimate
? () =>
projectEstimateService.getEstimateDetails(
workspaceSlug.toString(),
projectId.toString(),
projectDetails.estimate as string
)
: null
);
const estimateValue: any =
estimateKey || estimateKey === 0 ? estimateDetails?.points?.find((e) => e.key === estimateKey)?.value : "None";
return {
isEstimateActive: projectDetails?.estimate ? true : false,
estimatePoints: orderArrayBy(estimateDetails?.points ?? [], "key"),
estimateValue,
};
};
export default useEstimateOption;

View File

@ -6,6 +6,7 @@ import useSWR from "swr";
import {
useApplication,
useCycle,
useEstimate,
useLabel,
useMember,
useModule,
@ -30,7 +31,6 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
const { children } = props;
// store
const {
projectEstimates: { fetchProjectEstimates },
inbox: { fetchInboxesList, isInboxEnabled },
} = useMobxStore();
const {
@ -50,6 +50,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
const {
project: { fetchProjectLabels },
} = useLabel();
const { fetchProjectEstimates } = useEstimate();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;

View File

@ -1,7 +1,7 @@
// services
import { APIService } from "services/api.service";
// types
import type { IEstimate, IEstimateFormData } from "types";
import type { IEstimate, IEstimateFormData, IEstimatePoint } from "types";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
@ -10,7 +10,14 @@ export class ProjectEstimateService extends APIService {
super(API_BASE_URL);
}
async createEstimate(workspaceSlug: string, projectId: string, data: IEstimateFormData): Promise<any> {
async createEstimate(
workspaceSlug: string,
projectId: string,
data: IEstimateFormData
): Promise<{
estimate: IEstimate;
estimate_points: IEstimatePoint[];
}> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, data)
.then((response) => response?.data)
.catch((error) => {

228
web/store/estimate.store.ts Normal file
View File

@ -0,0 +1,228 @@
import { observable, action, makeObservable, runInAction, computed } from "mobx";
import { set } from "lodash";
// services
import { ProjectEstimateService } from "services/project";
// types
import { RootStore } from "store/root.store";
import { IEstimate, IEstimateFormData } from "types";
export interface IProjectEstimateStore {
// states
loader: boolean;
error: any | null;
// observables
estimates: Record<string, IEstimate[] | null>;
// computed
areEstimatesEnabledForCurrentProject: boolean;
projectEstimates: IEstimate[] | null;
activeEstimateDetails: IEstimate | null;
// computed actions
getEstimatePointValue: (estimateKey: number | null) => string;
getProjectEstimateById: (estimateId: string) => IEstimate | null;
// actions
fetchProjectEstimates: (workspaceSlug: string, projectId: string) => Promise<IEstimate[]>;
createEstimate: (workspaceSlug: string, projectId: string, data: IEstimateFormData) => Promise<IEstimate>;
updateEstimate: (
workspaceSlug: string,
projectId: string,
estimateId: string,
data: IEstimateFormData
) => Promise<IEstimate>;
deleteEstimate: (workspaceSlug: string, projectId: string, estimateId: string) => Promise<void>;
}
export class ProjectEstimatesStore implements IProjectEstimateStore {
// states
loader: boolean = false;
error: any | null = null;
// observables
estimates: Record<string, IEstimate[] | null> = {};
// root store
rootStore;
// services
estimateService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// states
loader: observable,
error: observable,
// observables
estimates: observable,
// computed
areEstimatesEnabledForCurrentProject: computed,
projectEstimates: computed,
activeEstimateDetails: computed,
// computed actions
getProjectEstimateById: action,
getEstimatePointValue: action,
// actions
fetchProjectEstimates: action,
createEstimate: action,
updateEstimate: action,
deleteEstimate: action,
});
// root store
this.rootStore = _rootStore;
// services
this.estimateService = new ProjectEstimateService();
}
/**
* @description returns true if estimates are enabled for current project, false otherwise
*/
get areEstimatesEnabledForCurrentProject() {
const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails;
if (!currentProjectDetails) return false;
return Boolean(currentProjectDetails?.estimate);
}
/**
* @description returns the list of estimates for current project
*/
get projectEstimates() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return null;
return this.estimates?.[projectId] || null;
}
/**
* @description returns the active estimate details for current project
*/
get activeEstimateDetails() {
const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails;
if (!currentProjectDetails || !currentProjectDetails?.estimate) return null;
return this.projectEstimates?.find((estimate) => estimate.id === currentProjectDetails?.estimate) || null;
}
/**
* @description returns the point value for the given estimate key to display in the UI
*/
getEstimatePointValue = (estimateKey: number | null) => {
if (estimateKey === null) return "None";
const activeEstimate = this.activeEstimateDetails;
return activeEstimate?.points?.find((point) => point.key === estimateKey)?.value || "None";
};
/**
* @description returns the estimate details for the given estimate id
*/
getProjectEstimateById = (estimateId: string) => {
if (!this.projectEstimates) return null;
const estimateInfo = this.projectEstimates?.find((estimate) => estimate.id === estimateId) || null;
return estimateInfo;
};
/**
* @description fetches the list of estimates for the given project
* @param workspaceSlug
* @param projectId
*/
fetchProjectEstimates = async (workspaceSlug: string, projectId: string) => {
try {
this.loader = true;
this.error = null;
const estimatesResponse = await this.estimateService.getEstimatesList(workspaceSlug, projectId);
runInAction(() => {
set(this.estimates, projectId, estimatesResponse);
this.loader = false;
this.error = null;
});
return estimatesResponse;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
/**
* @description creates a new estimate for the given project
* @param workspaceSlug
* @param projectId
* @param data
*/
createEstimate = async (workspaceSlug: string, projectId: string, data: IEstimateFormData) => {
try {
const response = await this.estimateService.createEstimate(workspaceSlug, projectId, data);
const responseEstimate = {
...response.estimate,
points: response.estimate_points,
};
runInAction(() => {
set(this.estimates, projectId, [responseEstimate, ...(this.estimates?.[projectId] || [])]);
});
return response.estimate;
} catch (error) {
console.log("Failed to create estimate from project store");
throw error;
}
};
/**
* @description updates the given estimate for the given project
* @param workspaceSlug
* @param projectId
* @param estimateId
* @param data
*/
updateEstimate = async (workspaceSlug: string, projectId: string, estimateId: string, data: IEstimateFormData) => {
try {
const updatedEstimates = (this.estimates?.[projectId] ?? []).map((estimate) =>
estimate.id === estimateId ? { ...estimate, ...data.estimate } : estimate
);
runInAction(() => {
set(this.estimates, projectId, updatedEstimates);
});
const response = await this.estimateService.patchEstimate(workspaceSlug, projectId, estimateId, data);
return response;
} catch (error) {
console.log("Failed to update estimate from project store");
this.fetchProjectEstimates(workspaceSlug, projectId);
throw error;
}
};
/**
* @description deletes the given estimate for the given project
* @param workspaceSlug
* @param projectId
* @param estimateId
*/
deleteEstimate = async (workspaceSlug: string, projectId: string, estimateId: string) => {
try {
const updatedEstimates = (this.estimates?.[projectId] ?? []).filter((estimate) => estimate.id !== estimateId);
runInAction(() => {
set(this.estimates, projectId, updatedEstimates);
});
await this.estimateService.deleteEstimate(workspaceSlug, projectId, estimateId);
} catch (error) {
console.log("Failed to delete estimate from project store");
this.fetchProjectEstimates(workspaceSlug, projectId);
}
};
}

View File

@ -13,6 +13,7 @@ import { IPageStore, PageStore } from "./page.store";
import { ILabelRootStore, LabelRootStore } from "./label";
import { IMemberRootStore, MemberRootStore } from "./member";
import { IInboxRootStore, InboxRootStore } from "./inbox";
import { IProjectEstimateStore, ProjectEstimatesStore } from "./estimate.store";
enableStaticRendering(typeof window === "undefined");
@ -30,6 +31,7 @@ export class RootStore {
page: IPageStore;
issue: IIssueRootStore;
state: IStateStore;
estimate: IProjectEstimateStore;
constructor() {
this.app = new AppRootStore(this);
@ -40,11 +42,12 @@ export class RootStore {
this.memberRoot = new MemberRootStore(this);
this.inboxRoot = new InboxRootStore(this);
// independent stores
this.state = new StateStore(this);
this.issue = new IssueRootStore(this);
this.cycle = new CycleStore(this);
this.module = new ModulesStore(this);
this.projectView = new ProjectViewStore(this);
this.page = new PageStore(this);
this.issue = new IssueRootStore(this);
this.state = new StateStore(this);
this.estimate = new ProjectEstimatesStore(this);
}
}

View File

@ -1,24 +1,24 @@
export interface IEstimate {
id: string;
created_at: Date;
updated_at: Date;
name: string;
description: string;
created_by: string;
updated_by: string;
points: IEstimatePoint[];
description: string;
id: string;
name: string;
project: string;
project_detail: IProject;
updated_at: Date;
updated_by: string;
points: IEstimatePoint[];
workspace: string;
workspace_detail: IWorkspace;
}
export interface IEstimatePoint {
id: string;
created_at: string;
created_by: string;
description: string;
estimate: string;
id: string;
key: number;
project: string;
updated_at: string;