From 1cd31f4705e6148341a5f71bdd519273fa6f3f67 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Mon, 27 May 2024 15:51:27 +0530 Subject: [PATCH] chore: integrated new endpoints --- packages/types/src/estimate.d.ts | 8 +- web/components/estimates/create/modal.tsx | 4 + web/components/estimates/create/stage-two.tsx | 15 +- web/components/estimates/index.ts | 3 +- web/components/estimates/points/create.tsx | 95 +++++++++++++ web/components/estimates/points/delete.tsx | 100 +++++++++++++ web/components/estimates/points/edit-root.tsx | 29 ++++ .../estimates/points/estimate-point-item.tsx | 119 ++++++++++++---- web/components/estimates/points/index.ts | 8 ++ web/components/estimates/points/update.tsx | 100 +++++++++++++ web/components/estimates/update/modal.tsx | 70 ++------- web/components/estimates/update/stage-two.tsx | 13 +- web/services/project/estimate.service.ts | 50 +++++-- web/store/estimates/estimate-point.ts | 44 +++++- web/store/estimates/estimate.ts | 134 +++++++++++++----- web/store/estimates/project-estimate.store.ts | 65 ++++----- 16 files changed, 670 insertions(+), 187 deletions(-) create mode 100644 web/components/estimates/points/create.tsx create mode 100644 web/components/estimates/points/delete.tsx create mode 100644 web/components/estimates/points/edit-root.tsx create mode 100644 web/components/estimates/points/index.ts create mode 100644 web/components/estimates/points/update.tsx diff --git a/packages/types/src/estimate.d.ts b/packages/types/src/estimate.d.ts index d42c7459a..571e48433 100644 --- a/packages/types/src/estimate.d.ts +++ b/packages/types/src/estimate.d.ts @@ -1,4 +1,3 @@ -import { IWorkspace, IProject } from "./"; import { EEstimateSystem, EEstimateUpdateStages } from "./enums"; export interface IEstimatePoint { @@ -27,9 +26,8 @@ export interface IEstimate { type: TEstimateSystemKeys | undefined; // categories, points, time points: IEstimatePoint[] | undefined; workspace: string | undefined; - workspace_detail: IWorkspace | undefined; project: string | undefined; - project_detail: IProject | undefined; + last_used: boolean | undefined; created_at: Date | undefined; updated_at: Date | undefined; created_by: string | undefined; @@ -38,7 +36,9 @@ export interface IEstimate { export interface IEstimateFormData { estimate?: { - type: string; + name?: string; + type?: string; + last_used?: boolean; }; estimate_points: { id?: string | undefined; diff --git a/web/components/estimates/create/modal.tsx b/web/components/estimates/create/modal.tsx index e1f955337..4162bfd42 100644 --- a/web/components/estimates/create/modal.tsx +++ b/web/components/estimates/create/modal.tsx @@ -59,7 +59,9 @@ export const CreateEstimateModal: FC = observer((props) => if (validatedEstimatePoints.length === estimatePoints?.length) { const payload: IEstimateFormData = { estimate: { + name: ESTIMATE_SYSTEMS[estimateSystem]?.name, type: estimateSystem, + last_used: true, }, estimate_points: validatedEstimatePoints, }; @@ -122,6 +124,8 @@ export const CreateEstimateModal: FC = observer((props) => )} {estimatePoints && ( void; }; -export const EstimateCreateStageTwo: FC = (props) => { - const { estimateSystem, estimatePoints, handleEstimatePoints } = props; +export const EstimateCreateStageTwo: FC = observer((props) => { + const { workspaceSlug, projectId, estimateSystem, estimatePoints, handleEstimatePoints } = props; const currentEstimateSystem = ESTIMATE_SYSTEMS[estimateSystem] || undefined; @@ -22,7 +25,7 @@ export const EstimateCreateStageTwo: FC = (props) => { const currentEstimationPoints = estimatePoints; const newEstimationPoint: TEstimatePointsObject = { key: currentEstimationPoints.length + 1, - value: "0", + value: "", }; handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]); }; @@ -59,11 +62,15 @@ export const EstimateCreateStageTwo: FC = (props) => { data={estimatePoints} render={(value: TEstimatePointsObject, index: number) => ( editEstimationPoint(index, value)} deleteItem={() => deleteEstimationPoint(index)} + handleEstimatePoints={handleEstimatePoints} /> )} onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))} @@ -77,4 +84,4 @@ export const EstimateCreateStageTwo: FC = (props) => { ); -}; +}); diff --git a/web/components/estimates/index.ts b/web/components/estimates/index.ts index d5f4c255b..4723f24a2 100644 --- a/web/components/estimates/index.ts +++ b/web/components/estimates/index.ts @@ -17,5 +17,4 @@ export * from "./create"; export * from "./update"; // estimate points -export * from "./points/estimate-point-item"; -export * from "./points/inline-editable"; +export * from "./points"; diff --git a/web/components/estimates/points/create.tsx b/web/components/estimates/points/create.tsx new file mode 100644 index 000000000..1aff50003 --- /dev/null +++ b/web/components/estimates/points/create.tsx @@ -0,0 +1,95 @@ +import { FC, useState } from "react"; +import { observer } from "mobx-react"; +import { Check, X } from "lucide-react"; +import { Spinner } from "@plane/ui"; +// constants +import { EEstimateSystem } from "@/constants/estimates"; +// hooks +import { useEstimate } from "@/hooks/store"; + +type TEstimatePointCreate = { + workspaceSlug: string; + projectId: string; + estimateId: string; + estimatePointId: string | undefined; +}; + +export const EstimatePointCreate: FC = observer((props) => { + const { workspaceSlug, projectId, estimateId, estimatePointId } = props; + // hooks + const { asJson: estimate, estimatePointIds, creteEstimatePoint } = useEstimate(estimateId); + // states + const [loader, setLoader] = useState(false); + const [estimateValue, setEstimateValue] = useState(""); + + const handleCreate = async () => { + if (estimatePointId) { + if (!workspaceSlug || !projectId || !projectId || !estimatePointIds) return; + try { + const estimateType: EEstimateSystem | undefined = estimate?.type; + let isEstimateValid = false; + if (estimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(estimateType)) { + if (estimateValue && Number(estimateValue) && Number(estimateValue) >= 0) { + isEstimateValid = true; + } + } else if (estimateType && estimateType === EEstimateSystem.CATEGORIES) { + if (estimateValue && estimateValue.length > 0) { + isEstimateValid = true; + } + } + + if (isEstimateValid) { + setLoader(true); + const payload = { + key: estimatePointIds?.length + 1, + value: estimateValue, + }; + await creteEstimatePoint(workspaceSlug, projectId, payload); + setLoader(false); + handleClose(); + } else { + console.log("please enter a valid estimate value"); + } + } catch { + setLoader(false); + console.log("something went wrong. please try again later"); + } + } else { + } + }; + + const handleClose = () => { + setEstimateValue(""); + }; + + return ( +
+
+ setEstimateValue(e.target.value)} + className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent" + /> +
+ {loader ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ +
+
+ ); +}); diff --git a/web/components/estimates/points/delete.tsx b/web/components/estimates/points/delete.tsx new file mode 100644 index 000000000..5cca9dab1 --- /dev/null +++ b/web/components/estimates/points/delete.tsx @@ -0,0 +1,100 @@ +import { FC, useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import { Check, X } from "lucide-react"; +import { Spinner } from "@plane/ui"; +// constants +import { EEstimateSystem } from "@/constants/estimates"; +// hooks +import { useEstimate, useEstimatePoint } from "@/hooks/store"; + +type TEstimatePointDelete = { + workspaceSlug: string; + projectId: string; + estimateId: string; + estimatePointId: string | undefined; +}; + +export const EstimatePointDelete: FC = observer((props) => { + const { workspaceSlug, projectId, estimateId, estimatePointId } = props; + // hooks + const { asJson: estimate, estimatePointIds } = useEstimate(estimateId); + const { asJson: estimatePoint, updateEstimatePoint } = useEstimatePoint(estimateId, estimatePointId); + // states + const [loader, setLoader] = useState(false); + const [estimateValue, setEstimateValue] = useState(undefined); + + useEffect(() => { + if (estimateValue === undefined) setEstimateValue(estimatePoint?.value || ""); + }, [estimateValue, estimatePoint]); + + const handleCreate = async () => { + if (estimatePointId) { + if (!workspaceSlug || !projectId || !projectId || !estimatePointIds) return; + try { + const estimateType: EEstimateSystem | undefined = estimate?.type; + let isEstimateValid = false; + if (estimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(estimateType)) { + if (estimateValue && Number(estimateValue) && Number(estimateValue) >= 0) { + isEstimateValid = true; + } + } else if (estimateType && estimateType === EEstimateSystem.CATEGORIES) { + if (estimateValue && estimateValue.length > 0) { + isEstimateValid = true; + } + } + + if (isEstimateValid) { + setLoader(true); + const payload = { + key: estimatePointIds?.length + 1, + value: estimateValue, + }; + await updateEstimatePoint(workspaceSlug, projectId, payload); + setLoader(false); + handleClose(); + } else { + console.log("please enter a valid estimate value"); + } + } catch { + setLoader(false); + console.log("something went wrong. please try again later"); + } + } else { + } + }; + + const handleClose = () => { + setEstimateValue(""); + }; + + return ( +
+
+ setEstimateValue(e.target.value)} + className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent" + /> +
+ {loader ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ +
+
+ ); +}); diff --git a/web/components/estimates/points/edit-root.tsx b/web/components/estimates/points/edit-root.tsx new file mode 100644 index 000000000..7ff161f68 --- /dev/null +++ b/web/components/estimates/points/edit-root.tsx @@ -0,0 +1,29 @@ +import { FC, useState } from "react"; +import { observer } from "mobx-react"; +import { Draggable } from "@plane/ui"; +// constants +import { EEstimateUpdateStages } from "@/constants/estimates"; + +type TEstimatePointEditRoot = { + workspaceSlug: string; + projectId: string; + estimateId: string; + mode: EEstimateUpdateStages; +}; + +type TEstimatePointEditingState = "update" | "delete"; + +export const EstimatePointEditRoot: FC = observer((props) => { + // props + const { workspaceSlug, projectId, estimateId, mode } = props; + // hooks + // states + const [editingState, setEditingState] = useState(undefined); + + const [estimateEditLoader, setEstimateEditLoader] = useState(false); + const [deletedEstimateValue, setDeletedEstimateValue] = useState(undefined); + const [isEstimateEditing, setIsEstimateEditing] = useState(false); + const [isEstimateDeleting, setIsEstimateDeleting] = useState(false); + + return ; +}); diff --git a/web/components/estimates/points/estimate-point-item.tsx b/web/components/estimates/points/estimate-point-item.tsx index cf32d7fae..899352d21 100644 --- a/web/components/estimates/points/estimate-point-item.tsx +++ b/web/components/estimates/points/estimate-point-item.tsx @@ -8,22 +8,37 @@ import { Draggable, Spinner } from "@plane/ui"; import { EEstimateUpdateStages } from "@/constants/estimates"; // helpers import { cn } from "@/helpers/common.helper"; -import { useEstimate } from "@/hooks/store"; +import { useEstimate, useEstimatePoint } from "@/hooks/store"; type TEstimatePointItem = { + workspaceSlug: string; + projectId: string; estimateId: string | undefined; mode: EEstimateUpdateStages; item: TEstimatePointsObject; + estimatePoints: TEstimatePointsObject[]; editItem: (value: string) => void; deleteItem: () => void; + handleEstimatePoints: (value: TEstimatePointsObject[]) => void; }; export const EstimatePointItem: FC = observer((props) => { // props - const { estimateId, mode, item, editItem, deleteItem } = props; + const { + workspaceSlug, + projectId, + estimateId, + mode, + item, + estimatePoints, + editItem, + deleteItem, + handleEstimatePoints, + } = props; const { id, key, value } = item; // hooks - const { asJson: estimate, updateEstimate, deleteEstimate } = useEstimate(estimateId); + const { asJson: estimate, creteEstimatePoint, deleteEstimatePoint } = useEstimate(estimateId); + const { updateEstimatePoint } = useEstimatePoint(estimateId, id); // ref const inputRef = useRef(null); // states @@ -35,7 +50,7 @@ export const EstimatePointItem: FC = observer((props) => { const [isEstimateDeleting, setIsEstimateDeleting] = useState(false); useEffect(() => { - if (inputValue === undefined || inputValue != value) setInputValue(value); + if (value && inputValue === undefined) setInputValue(value); }, [value, inputValue]); const handleCreateEdit = (value: string) => { @@ -43,11 +58,26 @@ export const EstimatePointItem: FC = observer((props) => { editItem(value); }; + const handleNewEstimatePoint = async () => { + if (inputValue) { + try { + setEstimateEditLoader(true); + const estimatePoint = await creteEstimatePoint(workspaceSlug, projectId, { key: key, value: inputValue }); + if (estimatePoint) + handleEstimatePoints([...estimatePoints, { id: estimatePoint.id, key: key, value: inputValue }]); + setIsEstimateEditing(false); + setEstimateEditLoader(false); + } catch (error) { + setEstimateEditLoader(false); + } + } + }; + const handleEdit = async () => { if (id) { try { setEstimateEditLoader(true); - await updateEstimate({ estimate_points: [{ id: id, key: key, value: value }] }); + await updateEstimatePoint(workspaceSlug, projectId, { key: key, value: inputValue }); setIsEstimateEditing(false); setEstimateEditLoader(false); } catch (error) { @@ -62,7 +92,7 @@ export const EstimatePointItem: FC = observer((props) => { if (id) { try { setEstimateEditLoader(true); - await deleteEstimate(deletedEstimateValue); + await deleteEstimatePoint(workspaceSlug, projectId, id, deletedEstimateValue); setIsEstimateDeleting(false); setEstimateEditLoader(false); } catch (error) { @@ -78,24 +108,65 @@ export const EstimatePointItem: FC = observer((props) => { return ( {!id && ( -
-
- -
- handleCreateEdit(e.target.value)} - className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5 w-full" - /> -
- -
-
+ <> + {mode === EEstimateUpdateStages.CREATE && ( +
+
+ +
+ handleCreateEdit(e.target.value)} + className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5 w-full" + /> +
+ +
+
+ )} + + {mode === EEstimateUpdateStages.EDIT && ( +
+
+ setInputValue(e.target.value)} + className={cn( + "border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full", + isEstimateDeleting ? `bg-custom-background-90` : `bg-transparent` + )} + disabled={isEstimateDeleting} + /> +
+ {estimateEditLoader ? ( +
+ +
+ ) : ( +
+ +
+ )} + +
+ +
+
+ )} + )} {id && ( diff --git a/web/components/estimates/points/index.ts b/web/components/estimates/points/index.ts new file mode 100644 index 000000000..adcc5f475 --- /dev/null +++ b/web/components/estimates/points/index.ts @@ -0,0 +1,8 @@ +export * from "./estimate-point-item"; +export * from "./inline-editable"; + +export * from "./edit-root"; + +export * from "./create"; +export * from "./update"; +export * from "./delete"; diff --git a/web/components/estimates/points/update.tsx b/web/components/estimates/points/update.tsx new file mode 100644 index 000000000..149a9aaf3 --- /dev/null +++ b/web/components/estimates/points/update.tsx @@ -0,0 +1,100 @@ +import { FC, useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import { Check, X } from "lucide-react"; +import { Spinner } from "@plane/ui"; +// constants +import { EEstimateSystem } from "@/constants/estimates"; +// hooks +import { useEstimate, useEstimatePoint } from "@/hooks/store"; + +type TEstimatePointUpdate = { + workspaceSlug: string; + projectId: string; + estimateId: string; + estimatePointId: string | undefined; +}; + +export const EstimatePointUpdate: FC = observer((props) => { + const { workspaceSlug, projectId, estimateId, estimatePointId } = props; + // hooks + const { asJson: estimate, estimatePointIds } = useEstimate(estimateId); + const { asJson: estimatePoint, updateEstimatePoint } = useEstimatePoint(estimateId, estimatePointId); + // states + const [loader, setLoader] = useState(false); + const [estimateValue, setEstimateValue] = useState(undefined); + + useEffect(() => { + if (estimateValue === undefined) setEstimateValue(estimatePoint?.value || ""); + }, [estimateValue, estimatePoint]); + + const handleCreate = async () => { + if (estimatePointId) { + if (!workspaceSlug || !projectId || !projectId || !estimatePointIds) return; + try { + const estimateType: EEstimateSystem | undefined = estimate?.type; + let isEstimateValid = false; + if (estimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(estimateType)) { + if (estimateValue && Number(estimateValue) && Number(estimateValue) >= 0) { + isEstimateValid = true; + } + } else if (estimateType && estimateType === EEstimateSystem.CATEGORIES) { + if (estimateValue && estimateValue.length > 0) { + isEstimateValid = true; + } + } + + if (isEstimateValid) { + setLoader(true); + const payload = { + key: estimatePointIds?.length + 1, + value: estimateValue, + }; + await updateEstimatePoint(workspaceSlug, projectId, payload); + setLoader(false); + handleClose(); + } else { + console.log("please enter a valid estimate value"); + } + } catch { + setLoader(false); + console.log("something went wrong. please try again later"); + } + } else { + } + }; + + const handleClose = () => { + setEstimateValue(""); + }; + + return ( +
+
+ setEstimateValue(e.target.value)} + className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent" + /> +
+ {loader ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ +
+
+ ); +}); diff --git a/web/components/estimates/update/modal.tsx b/web/components/estimates/update/modal.tsx index 1cc59884b..979bdfaef 100644 --- a/web/components/estimates/update/modal.tsx +++ b/web/components/estimates/update/modal.tsx @@ -1,13 +1,12 @@ import { FC, useEffect, useMemo, useState } from "react"; +import orderBy from "lodash/orderBy"; import { observer } from "mobx-react"; import { ChevronLeft } from "lucide-react"; -import { IEstimateFormData, TEstimatePointsObject, TEstimateUpdateStageKeys, TEstimateSystemKeys } from "@plane/types"; -import { Button, TOAST_TYPE, setToast } from "@plane/ui"; +import { TEstimatePointsObject, TEstimateUpdateStageKeys } from "@plane/types"; +import { Button } from "@plane/ui"; // components import { EModalPosition, EModalWidth, ModalCore } from "@/components/core"; import { EstimateUpdateStageOne, EstimateUpdateStageTwo } from "@/components/estimates"; -// constants -import { EEstimateSystem } from "@/constants/estimates"; // hooks import { useEstimate, @@ -26,18 +25,19 @@ export const UpdateEstimateModal: FC = observer((props) => // props const { workspaceSlug, projectId, estimateId, isOpen, handleClose } = props; // hooks - const { asJson: currentEstimate, updateEstimate } = useEstimate(estimateId); + const { asJson: currentEstimate } = useEstimate(estimateId); // states const [estimateEditType, setEstimateEditType] = useState(undefined); const [estimatePoints, setEstimatePoints] = useState(undefined); const handleEstimateEditType = (type: TEstimateUpdateStageKeys) => { if (currentEstimate?.points && currentEstimate?.points.length > 0) { - const estimateValidatePoints: TEstimatePointsObject[] = []; + let estimateValidatePoints: TEstimatePointsObject[] = []; currentEstimate?.points.map( (point) => point.key && point.value && estimateValidatePoints.push({ id: point.id, key: point.key, value: point.value }) ); + estimateValidatePoints = orderBy(estimateValidatePoints, ["key"], ["asc"]); if (estimateValidatePoints.length > 0) { setEstimateEditType(type); setEstimatePoints(estimateValidatePoints); @@ -56,57 +56,6 @@ export const UpdateEstimateModal: FC = observer((props) => // derived values const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]); - const isNewEstimatePointsToCreate = - (estimatePoints || []).filter((point) => point.id === undefined).length > 0 ? true : false; - - const handleUpdateEstimate = async () => { - try { - if (!workspaceSlug || !projectId || !estimateId || currentEstimate?.type === undefined) return; - - const currentEstimatePoints = (estimatePoints || []).filter((point) => point.id === undefined); - const currentEstimationType: TEstimateSystemKeys = currentEstimate?.type; - const validatedEstimatePoints: TEstimatePointsObject[] = []; - - if ([EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(currentEstimationType)) { - currentEstimatePoints?.map((estimatePoint) => { - if ( - estimatePoint.value && - ((estimatePoint.value != "0" && Number(estimatePoint.value)) || estimatePoint.value === "0") - ) - validatedEstimatePoints.push(estimatePoint); - }); - } else { - currentEstimatePoints?.map((estimatePoint) => { - if (estimatePoint.value) validatedEstimatePoints.push(estimatePoint); - }); - } - - if (validatedEstimatePoints.length === currentEstimatePoints?.length) { - const payload: IEstimateFormData = { - estimate_points: validatedEstimatePoints, - }; - await updateEstimate(payload); - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Estimate system created", - message: "Created and Enabled successfully", - }); - handleClose(); - } else { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "something went wrong", - }); - } - } catch (error) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "something went wrong", - }); - } - }; return ( @@ -135,6 +84,8 @@ export const UpdateEstimateModal: FC = observer((props) => {!estimateEditType && } {estimateEditType && estimatePoints && ( = observer((props) => - {isNewEstimatePointsToCreate && ( - - )} diff --git a/web/components/estimates/update/stage-two.tsx b/web/components/estimates/update/stage-two.tsx index 2ecae1000..df58c51ab 100644 --- a/web/components/estimates/update/stage-two.tsx +++ b/web/components/estimates/update/stage-two.tsx @@ -1,4 +1,5 @@ import { FC } from "react"; +import cloneDeep from "lodash/cloneDeep"; import { observer } from "mobx-react"; import { Plus } from "lucide-react"; import { IEstimate, TEstimatePointsObject, TEstimateUpdateStageKeys } from "@plane/types"; @@ -9,6 +10,8 @@ import { EstimatePointItem } from "@/components/estimates"; import { EEstimateUpdateStages, maxEstimatesCount } from "@/constants/estimates"; type TEstimateUpdateStageTwo = { + workspaceSlug: string; + projectId: string; estimate: IEstimate; estimateEditType: TEstimateUpdateStageKeys | undefined; estimatePoints: TEstimatePointsObject[]; @@ -16,15 +19,15 @@ type TEstimateUpdateStageTwo = { }; export const EstimateUpdateStageTwo: FC = observer((props) => { - const { estimate, estimateEditType, estimatePoints, handleEstimatePoints } = props; + const { workspaceSlug, projectId, estimate, estimateEditType, estimatePoints, handleEstimatePoints } = props; const currentEstimateSystem = estimate || undefined; const addNewEstimationPoint = () => { - const currentEstimationPoints = estimatePoints; + const currentEstimationPoints = cloneDeep(estimatePoints); const newEstimationPoint: TEstimatePointsObject = { key: currentEstimationPoints.length + 1, - value: "0", + value: "", }; handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]); }; @@ -64,11 +67,15 @@ export const EstimateUpdateStageTwo: FC = observer((pro data={estimatePoints} render={(value: TEstimatePointsObject, index: number) => ( editEstimationPoint(index, value)} deleteItem={() => deleteEstimationPoint(index)} + handleEstimatePoints={handleEstimatePoints} /> )} onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))} diff --git a/web/services/project/estimate.service.ts b/web/services/project/estimate.service.ts index 014f29252..02ee78e47 100644 --- a/web/services/project/estimate.service.ts +++ b/web/services/project/estimate.service.ts @@ -1,5 +1,5 @@ // types -import { IEstimate, IEstimateFormData } from "@plane/types"; +import { IEstimate, IEstimateFormData, IEstimatePoint } from "@plane/types"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; // services @@ -10,7 +10,6 @@ export class EstimateService extends APIService { super(API_BASE_URL); } - // fetching the estimates in workspace level async fetchWorkspaceEstimates(workspaceSlug: string): Promise { try { const { data } = await this.get(`/api/workspaces/${workspaceSlug}/estimates/`); @@ -61,7 +60,7 @@ export class EstimateService extends APIService { workspaceSlug: string, projectId: string, estimateId: string, - payload: IEstimateFormData + payload: Partial ): Promise { try { const { data } = await this.patch( @@ -74,16 +73,51 @@ export class EstimateService extends APIService { } } + async createEstimatePoint( + workspaceSlug: string, + projectId: string, + estimateId: string, + payload: Partial + ): Promise { + try { + const { data } = await this.post( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-points/`, + payload + ); + return data || undefined; + } catch (error) { + throw error; + } + } + + async updateEstimatePoint( + workspaceSlug: string, + projectId: string, + estimateId: string, + estimatePointId: string, + payload: Partial + ): Promise { + try { + const { data } = await this.post( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-points/${estimatePointId}/`, + payload + ); + return data || undefined; + } catch (error) { + throw error; + } + } + async removeEstimatePoint( workspaceSlug: string, projectId: string, estimateId: string, estimatePointId: string, - payload: { new_estimate_id: string | undefined } - ): Promise { - return this.patch( - `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-point/${estimatePointId}/`, - payload + params?: { new_estimate_id: string | undefined } + ): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-points/${estimatePointId}/`, + params ) .then((response) => response?.data) .catch((error) => { diff --git a/web/store/estimates/estimate-point.ts b/web/store/estimates/estimate-point.ts index 0b03c8841..72181be0c 100644 --- a/web/store/estimates/estimate-point.ts +++ b/web/store/estimates/estimate-point.ts @@ -1,5 +1,6 @@ -import { action, computed, makeObservable, observable } from "mobx"; -import { IEstimateFormData, IEstimate, IEstimatePoint as IEstimatePointType } from "@plane/types"; +import set from "lodash/set"; +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import { IEstimate, IEstimatePoint as IEstimatePointType } from "@plane/types"; // services import { EstimateService } from "@/services/project/estimate.service"; // store @@ -16,7 +17,11 @@ export interface IEstimatePoint extends IEstimatePointType { // computed asJson: IEstimatePointType; // actions - updateEstimatePoint: (payload: IEstimateFormData) => Promise; + updateEstimatePoint: ( + workspaceSlug: string, + projectId: string, + payload: Partial + ) => Promise; } export class EstimatePoint implements IEstimatePoint { @@ -73,7 +78,6 @@ export class EstimatePoint implements IEstimatePoint { this.updated_at = this.data.updated_at; this.created_by = this.data.created_by; this.updated_by = this.data.updated_by; - // service this.service = new EstimateService(); } @@ -96,10 +100,36 @@ export class EstimatePoint implements IEstimatePoint { } // actions - updateEstimatePoint = async (payload: IEstimateFormData) => { + /** + * @description updating an estimate point + * @param { Partial } payload + * @returns { IEstimatePointType | undefined } + */ + updateEstimatePoint = async ( + workspaceSlug: string, + projectId: string, + payload: Partial + ): Promise => { try { - const { workspaceSlug, projectId } = this.store.router; - if (!workspaceSlug || !projectId || !this.projectEstimate?.id || !this.id || !payload) return undefined; + if (!this.projectEstimate?.id || !this.id || !payload) return undefined; + + const estimatePoint = await this.service.updateEstimatePoint( + workspaceSlug, + projectId, + this.projectEstimate?.id, + this.id, + payload + ); + if (estimatePoint) { + runInAction(() => { + Object.keys(payload).map((key) => { + const estimatePointKey = key as keyof IEstimatePointType; + set(this, estimatePointKey, estimatePoint[estimatePointKey]); + }); + }); + } + + return estimatePoint; } catch (error) { throw error; } diff --git a/web/store/estimates/estimate.ts b/web/store/estimates/estimate.ts index 86b09f39f..ad304d01b 100644 --- a/web/store/estimates/estimate.ts +++ b/web/store/estimates/estimate.ts @@ -1,16 +1,8 @@ import set from "lodash/set"; import unset from "lodash/unset"; -import update from "lodash/update"; import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; -import { - IEstimate as IEstimateType, - IEstimatePoint as IEstimatePointType, - IProject, - IWorkspace, - TEstimateSystemKeys, - IEstimateFormData, -} from "@plane/types"; +import { IEstimate as IEstimateType, IEstimatePoint as IEstimatePointType, TEstimateSystemKeys } from "@plane/types"; // services import { EstimateService } from "@/services/project/estimate.service"; // store @@ -31,8 +23,22 @@ export interface IEstimate extends IEstimateType { estimatePointIds: string[] | undefined; estimatePointById: (estimateId: string) => IEstimatePointType | undefined; // actions - updateEstimate: (payload: IEstimateFormData) => Promise; - deleteEstimate: (estimatePointId: string | undefined) => Promise; + updateEstimate: ( + workspaceSlug: string, + projectId: string, + payload: Partial + ) => Promise; + creteEstimatePoint: ( + workspaceSlug: string, + projectId: string, + payload: Partial + ) => Promise; + deleteEstimatePoint: ( + workspaceSlug: string, + projectId: string, + estimatePointId: string, + newEstimatePointId: string | undefined + ) => Promise; } export class Estimate implements IEstimate { @@ -43,9 +49,8 @@ export class Estimate implements IEstimate { type: TEstimateSystemKeys | undefined = undefined; points: IEstimatePointType[] | undefined = undefined; workspace: string | undefined = undefined; - workspace_detail: IWorkspace | undefined = undefined; project: string | undefined = undefined; - project_detail: IProject | undefined = undefined; + last_used: boolean | undefined = undefined; created_at: Date | undefined = undefined; updated_at: Date | undefined = undefined; created_by: string | undefined = undefined; @@ -68,9 +73,8 @@ export class Estimate implements IEstimate { type: observable.ref, points: observable, workspace: observable.ref, - workspace_detail: observable, project: observable.ref, - project_detail: observable, + last_used: observable.ref, created_at: observable.ref, updated_at: observable.ref, created_by: observable.ref, @@ -83,7 +87,8 @@ export class Estimate implements IEstimate { estimatePointIds: computed, // actions updateEstimate: action, - deleteEstimate: action, + creteEstimatePoint: action, + deleteEstimatePoint: action, }); this.id = this.data.id; this.name = this.data.name; @@ -91,14 +96,12 @@ export class Estimate implements IEstimate { this.type = this.data.type; this.points = this.data.points; this.workspace = this.data.workspace; - this.workspace_detail = this.data.workspace_detail; this.project = this.data.project; - this.project_detail = this.data.project_detail; + this.last_used = this.data.last_used; this.created_at = this.data.created_at; this.updated_at = this.data.updated_at; this.created_by = this.data.created_by; this.updated_by = this.data.updated_by; - this.data.points?.forEach((estimationPoint) => { if (estimationPoint.id) set(this.estimatePoints, [estimationPoint.id], new EstimatePoint(this.store, this.data, estimationPoint)); @@ -116,9 +119,8 @@ export class Estimate implements IEstimate { type: this.type, points: this.points, workspace: this.workspace, - workspace_detail: this.workspace_detail, project: this.project, - project_detail: this.project_detail, + last_used: this.last_used, created_at: this.created_at, updated_at: this.updated_at, created_by: this.created_by, @@ -143,35 +145,95 @@ export class Estimate implements IEstimate { }); // actions - updateEstimate = async (payload: IEstimateFormData) => { + /** + * @description update an estimate + * @param { string } workspaceSlug + * @param { string } projectId + * @param { Partial } payload + * @returns { IEstimateType | undefined } + */ + updateEstimate = async ( + workspaceSlug: string, + projectId: string, + payload: Partial + ): Promise => { try { - const { workspaceSlug, projectId } = this.store.router; - if (!workspaceSlug || !projectId || !this.id || !payload) return; + if (!this.id || !payload) return; - await this.service.updateEstimate(workspaceSlug, projectId, this.id, payload); + const estimate = await this.service.updateEstimate(workspaceSlug, projectId, this.id, payload); + if (estimate) { + runInAction(() => { + Object.keys(payload).map((key) => { + const estimateKey = key as keyof IEstimateType; + set(this, estimateKey, estimate[estimateKey]); + }); + }); + } - // runInAction(() => { - // this.points = payload.estimate_points; - // this.data.points = payload.estimate_points; - // }); + return estimate; } catch (error) { throw error; } }; - deleteEstimate = async (estimatePointId: string | undefined) => { + /** + * @description create an estimate point + * @param { string } workspaceSlug + * @param { string } projectId + * @param { Partial } payload + * @returns { IEstimatePointType | undefined } + */ + creteEstimatePoint = async ( + workspaceSlug: string, + projectId: string, + payload: Partial + ): Promise => { try { - const { workspaceSlug, projectId } = this.store.router; - if (!workspaceSlug || !projectId || !estimatePointId) return; + if (!this.id || !payload) return; - // make delete estimation request + const estimatePoint = await this.service.createEstimatePoint(workspaceSlug, projectId, this.id, payload); + if (estimatePoint) { + runInAction(() => { + if (estimatePoint.id) { + set(this.estimatePoints, [estimatePoint.id], new EstimatePoint(this.store, this.data, estimatePoint)); + } + }); + } + } catch (error) { + throw error; + } + }; + + /** + * @description delete an estimate point + * @param { string } workspaceSlug + * @param { string } projectId + * @param { string } estimatePointId + * @param { string | undefined } newEstimatePointId + * @returns { void } + */ + deleteEstimatePoint = async ( + workspaceSlug: string, + projectId: string, + estimatePointId: string, + newEstimatePointId: string | undefined + ) => { + try { + if (!this.id) return; + + const deleteEstimatePoint = await this.service.removeEstimatePoint( + workspaceSlug, + projectId, + this.id, + estimatePointId, + newEstimatePointId ? { new_estimate_id: newEstimatePointId } : undefined + ); runInAction(() => { - update(this, "points", (estimationPoints = []) => - estimationPoints.filter((point: IEstimatePointType) => point.id !== estimatePointId) - ); unset(this.estimatePoints, [estimatePointId]); }); + + return deleteEstimatePoint; } catch (error) { throw error; } diff --git a/web/store/estimates/project-estimate.store.ts b/web/store/estimates/project-estimate.store.ts index 3c894d8bb..f028b33b1 100644 --- a/web/store/estimates/project-estimate.store.ts +++ b/web/store/estimates/project-estimate.store.ts @@ -1,5 +1,5 @@ +import orderBy from "lodash/orderBy"; import set from "lodash/set"; -import sortBy from "lodash/sortBy"; import update from "lodash/update"; import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; @@ -59,7 +59,6 @@ export class ProjectEstimateStore implements IProjectEstimateStore { // computed currentActiveEstimateId: computed, archivedEstimateIds: computed, - projectEstimateIds: computed, // actions getWorkspaceEstimates: action, getProjectEstimates: action, @@ -79,9 +78,10 @@ export class ProjectEstimateStore implements IProjectEstimateStore { get currentActiveEstimateId(): string | undefined { const { projectId } = this.store.router; if (!projectId) return undefined; - const projectDetails = this.store.projectRoot.project.getProjectById(projectId); - if (!projectDetails) return undefined; - return projectDetails.estimate ?? undefined; + const currentActiveEstimateId = Object.values(this.estimates || {}).find( + (p) => p.project === projectId && p.last_used + ); + return currentActiveEstimateId?.id ?? undefined; } /** @@ -91,25 +91,15 @@ export class ProjectEstimateStore implements IProjectEstimateStore { get archivedEstimateIds(): string[] | undefined { const { projectId } = this.store.router; if (!projectId) return undefined; - const archivedEstimateIds = Object.values(this.estimates || {}) - .filter((p) => p.project === projectId && p.id !== this.currentActiveEstimateId) - .map((p) => p.id) as string[]; + const archivedEstimates = orderBy( + Object.values(this.estimates || {}).filter((p) => p.project === projectId && !p.last_used), + ["created_at"], + "desc" + ); + const archivedEstimateIds = archivedEstimates.map((p) => p.id) as string[]; return archivedEstimateIds ?? undefined; } - /** - * @description get all estimate ids for a project - * @returns { string[] | undefined } - */ - get projectEstimateIds(): string[] | undefined { - const { projectId } = this.store.router; - if (!projectId) return undefined; - const projectEstimatesIds = Object.values(this.estimates || {}) - .filter((p) => p.project === projectId) - .map((p) => p.id) as string[]; - return projectEstimatesIds ?? undefined; - } - /** * @description get estimates are enabled in the project or not * @returns { boolean } @@ -145,6 +135,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore { // actions /** * @description fetch all estimates for a workspace + * @param { string } workspaceSlug * @returns { IEstimateType[] | undefined } */ getWorkspaceEstimates = async ( @@ -153,7 +144,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore { ): Promise => { try { this.error = undefined; - if (!this.projectEstimateIds) this.loader = loader ? loader : "init-loader"; + if (Object.keys(this.estimates || {}).length <= 0) this.loader = loader ? loader : "init-loader"; const estimates = await this.service.fetchWorkspaceEstimates(workspaceSlug); if (estimates && estimates.length > 0) { @@ -176,6 +167,8 @@ export class ProjectEstimateStore implements IProjectEstimateStore { /** * @description fetch all estimates for a project + * @param { string } workspaceSlug + * @param { string } projectId * @returns { IEstimateType[] | undefined } */ getProjectEstimates = async ( @@ -185,7 +178,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore { ): Promise => { try { this.error = undefined; - if (!this.projectEstimateIds) this.loader = loader ? loader : "init-loader"; + if (!this.estimateIdsByProjectId(projectId)) this.loader = loader ? loader : "init-loader"; const estimates = await this.service.fetchProjectEstimates(workspaceSlug, projectId); if (estimates && estimates.length > 0) { @@ -208,6 +201,8 @@ export class ProjectEstimateStore implements IProjectEstimateStore { /** * @description update an estimate for a project + * @param { string } workspaceSlug + * @param { string } projectId * @param { string } estimateId * @returns IEstimateType | undefined */ @@ -241,7 +236,9 @@ export class ProjectEstimateStore implements IProjectEstimateStore { /** * @description create an estimate for a project - * @param { Partial } payload + * @param { string } workspaceSlug + * @param { string } projectId + * @param { Partial } payload * @returns */ createEstimate = async ( @@ -253,21 +250,15 @@ export class ProjectEstimateStore implements IProjectEstimateStore { this.error = undefined; const estimate = await this.service.createEstimate(workspaceSlug, projectId, payload); - // FIXME: i am getting different response from the server and once backend changes remove the get request and uncomment the commented code - let estimates = await this.getProjectEstimates(workspaceSlug, projectId, "mutation-loader"); - estimates = sortBy(estimates, "created_at"); - if (estimates && estimates.length > 0) + console.log("estimate", estimate); + if (estimate) { await this.store.projectRoot.project.updateProject(workspaceSlug, projectId, { - estimate: estimates[estimates.length - 1].id, + estimate: estimate.id, }); - // if (estimate) { - // await this.store.projectRoot.project.updateProject(workspaceSlug, projectId, { - // estimate: estimate.id, - // }); - // runInAction(() => { - // if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate)); - // }); - // } + runInAction(() => { + if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate)); + }); + } return estimate; } catch (error) {