From 9c279a62e0a108f9ab1180dd5eb7b40994d267a4 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Wed, 29 May 2024 13:01:59 +0530 Subject: [PATCH] chore: updated switch estimate --- apiserver/plane/app/serializers/estimate.py | 4 - apiserver/plane/app/views/estimate/base.py | 15 +-- .../estimates/estimate-list-item.tsx | 18 ++-- .../estimates/points/edit/delete.tsx | 10 +- .../estimates/points/switch/root.tsx | 101 ++++++++++++++---- web/components/estimates/update/modal.tsx | 41 ++++--- web/services/project/estimate.service.ts | 2 +- web/store/estimates/estimate-point.ts | 10 ++ web/store/estimates/estimate.ts | 76 +++++++------ 9 files changed, 187 insertions(+), 90 deletions(-) diff --git a/apiserver/plane/app/serializers/estimate.py b/apiserver/plane/app/serializers/estimate.py index 010382260..e73b5ceef 100644 --- a/apiserver/plane/app/serializers/estimate.py +++ b/apiserver/plane/app/serializers/estimate.py @@ -11,10 +11,6 @@ from rest_framework import serializers class EstimateSerializer(BaseSerializer): - workspace_detail = WorkspaceLiteSerializer( - read_only=True, source="workspace" - ) - project_detail = ProjectLiteSerializer(read_only=True, source="project") class Meta: model = Estimate diff --git a/apiserver/plane/app/views/estimate/base.py b/apiserver/plane/app/views/estimate/base.py index 173e7da49..2bd9e3dfe 100644 --- a/apiserver/plane/app/views/estimate/base.py +++ b/apiserver/plane/app/views/estimate/base.py @@ -122,7 +122,12 @@ class BulkEstimatePointEndpoint(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - _ = Estimate.objects.get(pk=estimate_id) + estimate = Estimate.objects.get(pk=estimate_id) + + if request.data.get("estimate"): + estimate.name = request.data.get("estimate").get("name", estimate.name) + estimate.type = request.data.get("estimate").get("type", estimate.type) + estimate.save() estimate_points_data = request.data.get("estimate_points", []) @@ -159,13 +164,9 @@ class BulkEstimatePointEndpoint(BaseViewSet): batch_size=10, ) - estimate_point_serializer = EstimatePointSerializer( - estimate_points, many=True - ) + estimate_serializer = EstimateReadSerializer(estimate) return Response( - { - "points": estimate_point_serializer.data, - }, + estimate_serializer.data, status=status.HTTP_200_OK, ) diff --git a/web/components/estimates/estimate-list-item.tsx b/web/components/estimates/estimate-list-item.tsx index 64db9b553..b0e4076fa 100644 --- a/web/components/estimates/estimate-list-item.tsx +++ b/web/components/estimates/estimate-list-item.tsx @@ -1,11 +1,10 @@ import { FC } from "react"; -import sortBy from "lodash/sortBy"; import { observer } from "mobx-react"; import { Pen } from "lucide-react"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useProjectEstimates } from "@/hooks/store"; +import { useEstimate, useProjectEstimates } from "@/hooks/store"; type TEstimateListItem = { estimateId: string; @@ -19,11 +18,16 @@ export const EstimateListItem: FC = observer((props) => { const { estimateId, isAdmin, isEstimateEnabled, isEditable, onEditClick } = props; // hooks const { estimateById } = useProjectEstimates(); - + const { estimatePointIds, estimatePointById } = useEstimate(estimateId); const currentEstimate = estimateById(estimateId); - if (!currentEstimate) return <>; + // derived values + const estimatePointValues = estimatePointIds?.map((estimatePointId) => { + const estimatePoint = estimatePointById(estimatePointId); + if (estimatePoint) return estimatePoint.value; + }); + if (!currentEstimate) return <>; return (
= observer((props) => { >

{currentEstimate?.name}

-

- {sortBy(currentEstimate?.points, ["key"]) - ?.map((estimatePoint) => estimatePoint?.value) - .join(", ")} -

+

{(estimatePointValues || [])?.join(", ")}

{isAdmin && isEditable && (
= observer((props) => { const { workspaceSlug, projectId, estimateId, estimatePointId, callback } = props; // hooks - const { asJson: estimate, deleteEstimatePoint } = useEstimate(estimateId); + const { estimatePointIds, estimatePointById, deleteEstimatePoint } = useEstimate(estimateId); const { asJson: estimatePoint } = useEstimatePoint(estimateId, estimatePointId); // states const [loader, setLoader] = useState(false); @@ -47,8 +47,12 @@ export const EstimatePointDelete: FC = observer((props) => }; // derived values - const selectDropdownOptions = - estimate && estimate?.points ? estimate?.points.filter((point) => point.id !== estimatePointId) : []; + const selectDropdownOptionIds = estimatePointIds?.filter((pointId) => pointId != estimatePointId) as string[]; + const selectDropdownOptions = (selectDropdownOptionIds || [])?.map((pointId) => { + const estimatePoint = estimatePointById(pointId); + if (estimatePoint && estimatePoint?.id) + return { id: estimatePoint.id, key: estimatePoint.key, value: estimatePoint.value }; + }); return (
diff --git a/web/components/estimates/points/switch/root.tsx b/web/components/estimates/points/switch/root.tsx index 57b840526..e3a17c543 100644 --- a/web/components/estimates/points/switch/root.tsx +++ b/web/components/estimates/points/switch/root.tsx @@ -1,25 +1,28 @@ import { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { TEstimatePointsObject } from "@plane/types"; +import { IEstimateFormData, TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // components import { EstimatePointItemSwitchPreview } from "@/components/estimates/points"; // constants -import { EEstimateSystem, EEstimateUpdateStages } from "@/constants/estimates"; +import { EEstimateSystem, EEstimateUpdateStages, ESTIMATE_SYSTEMS } from "@/constants/estimates"; // hooks import { useEstimate } from "@/hooks/store"; type TEstimatePointSwitchRoot = { + estimateSystemSwitchType: TEstimateSystemKeys; workspaceSlug: string; projectId: string; estimateId: string; + handleClose: () => void; mode?: EEstimateUpdateStages; }; export const EstimatePointSwitchRoot: FC = observer((props) => { // props - const { workspaceSlug, projectId, estimateId } = props; + const { estimateSystemSwitchType, workspaceSlug, projectId, estimateId, handleClose } = props; // hooks - const { asJson: estimate, estimatePointIds, estimatePointById } = useEstimate(estimateId); + const { asJson: estimate, estimatePointIds, estimatePointById, updateEstimateSwitch } = useEstimate(estimateId); // states const [estimatePoints, setEstimatePoints] = useState(undefined); @@ -41,26 +44,84 @@ export const EstimatePointSwitchRoot: FC = observer((p }); }; + const handleSwitchEstimate = async () => { + try { + if (!workspaceSlug || !projectId) return; + const validatedEstimatePoints: TEstimatePointsObject[] = []; + if ([EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateSystemSwitchType)) { + estimatePoints?.map((estimatePoint) => { + if ( + estimatePoint.value && + ((estimatePoint.value != "0" && Number(estimatePoint.value)) || estimatePoint.value === "0") + ) + validatedEstimatePoints.push(estimatePoint); + }); + } else { + estimatePoints?.map((estimatePoint) => { + if (estimatePoint.value) validatedEstimatePoints.push(estimatePoint); + }); + } + if (validatedEstimatePoints.length === estimatePoints?.length) { + const payload: IEstimateFormData = { + estimate: { + name: ESTIMATE_SYSTEMS[estimateSystemSwitchType]?.name, + type: estimateSystemSwitchType, + }, + estimate_points: validatedEstimatePoints, + }; + await updateEstimateSwitch(workspaceSlug, projectId, 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", + }); + } + }; + if (!workspaceSlug || !projectId || !estimateId || !estimatePoints) return <>; return ( -
-
-
Current {estimate?.type}
-
-
- New {estimate?.type === EEstimateSystem?.POINTS ? EEstimateSystem?.CATEGORIES : EEstimateSystem?.POINTS} + <> +
+
+
Current {estimate?.type}
+
+
New {estimateSystemSwitchType}
+ + {estimatePoints.map((estimateObject, index) => ( + handleEstimatePoints(index, value)} + /> + ))}
- {estimatePoints.map((estimateObject, index) => ( - handleEstimatePoints(index, value)} - /> - ))} -
+
+ + + +
+ ); }); diff --git a/web/components/estimates/update/modal.tsx b/web/components/estimates/update/modal.tsx index 4cd019325..882175c44 100644 --- a/web/components/estimates/update/modal.tsx +++ b/web/components/estimates/update/modal.tsx @@ -1,13 +1,15 @@ import { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { ChevronLeft } from "lucide-react"; -import { TEstimateUpdateStageKeys } from "@plane/types"; +import { TEstimateSystemKeys, TEstimateUpdateStageKeys } from "@plane/types"; import { Button } from "@plane/ui"; // components import { EModalPosition, EModalWidth, ModalCore } from "@/components/core"; import { EstimateUpdateStageOne, EstimatePointEditRoot, EstimatePointSwitchRoot } from "@/components/estimates"; // constants -import { EEstimateUpdateStages } from "@/constants/estimates"; +import { EEstimateSystem, EEstimateUpdateStages } from "@/constants/estimates"; +// hooks +import { useEstimate } from "@/hooks/store"; type TUpdateEstimateModal = { workspaceSlug: string; @@ -20,16 +22,26 @@ type TUpdateEstimateModal = { export const UpdateEstimateModal: FC = observer((props) => { // props const { workspaceSlug, projectId, estimateId, isOpen, handleClose } = props; + // hooks + const { asJson: estimate } = useEstimate(estimateId); // states const [estimateEditType, setEstimateEditType] = useState(undefined); + const [estimateSystemSwitchType, setEstimateSystemSwitchType] = useState(undefined); useEffect(() => { - if (!isOpen) setEstimateEditType(undefined); + if (!isOpen) { + setEstimateEditType(undefined); + setEstimateSystemSwitchType(undefined); + } }, [isOpen]); - const handleEstimateEditType = (type: TEstimateUpdateStageKeys) => setEstimateEditType(type); - - const handleSwitchEstimate = () => {}; + const handleEstimateEditType = (type: TEstimateUpdateStageKeys) => { + if (type === EEstimateUpdateStages.SWITCH && estimate?.type) + setEstimateSystemSwitchType( + estimate?.type === EEstimateSystem.CATEGORIES ? EEstimateSystem.POINTS : EEstimateSystem.CATEGORIES + ); + setEstimateEditType(type); + }; return ( @@ -62,23 +74,24 @@ export const UpdateEstimateModal: FC = observer((props) => {estimateEditType === EEstimateUpdateStages.EDIT && ( )} - {estimateEditType === EEstimateUpdateStages.SWITCH && ( - + {estimateEditType === EEstimateUpdateStages.SWITCH && estimateSystemSwitchType && ( + )} )}
- {[EEstimateUpdateStages.SWITCH, undefined].includes(estimateEditType) && ( + {estimateEditType === undefined && (
- {estimateEditType === EEstimateUpdateStages.SWITCH && ( - - )}
)}
diff --git a/web/services/project/estimate.service.ts b/web/services/project/estimate.service.ts index 38089f64e..e1af542ca 100644 --- a/web/services/project/estimate.service.ts +++ b/web/services/project/estimate.service.ts @@ -61,7 +61,7 @@ export class EstimateService extends APIService { projectId: string, estimateId: string, payload: Partial - ): Promise<{ points: IEstimatePoint[] } | undefined> { + ): Promise { try { const { data } = await this.patch( `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`, diff --git a/web/store/estimates/estimate-point.ts b/web/store/estimates/estimate-point.ts index 72181be0c..f2296c7bc 100644 --- a/web/store/estimates/estimate-point.ts +++ b/web/store/estimates/estimate-point.ts @@ -16,6 +16,8 @@ export interface IEstimatePoint extends IEstimatePointType { error: TErrorCodes | undefined; // computed asJson: IEstimatePointType; + // helper actions + updateEstimatePointObject: (estimatePoint: Partial) => void; // actions updateEstimatePoint: ( workspaceSlug: string, @@ -99,6 +101,14 @@ export class EstimatePoint implements IEstimatePoint { }; } + // helper actions + updateEstimatePointObject = (estimatePoint: Partial) => { + Object.keys(estimatePoint).map((key) => { + const estimatePointKey = key as keyof IEstimatePointType; + set(this, estimatePointKey, estimatePoint[estimatePointKey]); + }); + }; + // actions /** * @description updating an estimate point diff --git a/web/store/estimates/estimate.ts b/web/store/estimates/estimate.ts index ae97a9924..9db038edd 100644 --- a/web/store/estimates/estimate.ts +++ b/web/store/estimates/estimate.ts @@ -21,25 +21,25 @@ type TErrorCodes = { message?: string; }; -export interface IEstimate extends IEstimateType { +export interface IEstimate extends Omit { // observables error: TErrorCodes | undefined; estimatePoints: Record; // computed - asJson: IEstimateType; + asJson: Omit; estimatePointIds: string[] | undefined; estimatePointById: (estimatePointId: string) => IEstimatePointType | undefined; // actions - updateEstimate: ( - workspaceSlug: string, - projectId: string, - payload: Partial - ) => Promise; updateEstimateSortOrder: ( workspaceSlug: string, projectId: string, payload: TEstimatePointsObject[] - ) => Promise; + ) => Promise; + updateEstimateSwitch: ( + workspaceSlug: string, + projectId: string, + payload: IEstimateFormData + ) => Promise; creteEstimatePoint: ( workspaceSlug: string, projectId: string, @@ -59,7 +59,6 @@ export class Estimate implements IEstimate { name: string | undefined = undefined; description: string | undefined = undefined; type: TEstimateSystemKeys | undefined = undefined; - points: IEstimatePointType[] | undefined = undefined; workspace: string | undefined = undefined; project: string | undefined = undefined; last_used: boolean | undefined = undefined; @@ -83,7 +82,6 @@ export class Estimate implements IEstimate { name: observable.ref, description: observable.ref, type: observable.ref, - points: observable, workspace: observable.ref, project: observable.ref, last_used: observable.ref, @@ -98,8 +96,8 @@ export class Estimate implements IEstimate { asJson: computed, estimatePointIds: computed, // actions - updateEstimate: action, updateEstimateSortOrder: action, + updateEstimateSwitch: action, creteEstimatePoint: action, deleteEstimatePoint: action, }); @@ -107,7 +105,6 @@ export class Estimate implements IEstimate { this.name = this.data.name; this.description = this.data.description; this.type = this.data.type; - this.points = this.data.points; this.workspace = this.data.workspace; this.project = this.data.project; this.last_used = this.data.last_used; @@ -130,7 +127,6 @@ export class Estimate implements IEstimate { name: this.name, description: this.description, type: this.type, - points: this.points, workspace: this.workspace, project: this.project, last_used: this.last_used, @@ -159,22 +155,32 @@ export class Estimate implements IEstimate { // actions /** - * @description update an estimate + * @description update an estimate sort order * @param { string } workspaceSlug * @param { string } projectId - * @param { Partial } payload + * @param { TEstimatePointsObject[] } payload * @returns { IEstimateType | undefined } */ - updateEstimate = async ( + updateEstimateSortOrder = async ( workspaceSlug: string, projectId: string, - payload: Partial + payload: TEstimatePointsObject[] ): Promise => { try { if (!this.id || !payload) return; - const estimate = await this.service.updateEstimate(workspaceSlug, projectId, this.id, payload); - return estimate as any; + const estimate = await this.service.updateEstimate(workspaceSlug, projectId, this.id, { + estimate_points: payload, + }); + runInAction(() => { + estimate?.points && + estimate?.points.map((estimatePoint) => { + if (estimatePoint.id) + set(this.estimatePoints, [estimatePoint.id], new EstimatePoint(this.store, this.data, estimatePoint)); + }); + }); + + return estimate; } catch (error) { throw error; } @@ -184,28 +190,34 @@ export class Estimate implements IEstimate { * @description update an estimate sort order * @param { string } workspaceSlug * @param { string } projectId - * @param { Partial } payload - * @returns { void } + * @param { IEstimateFormData} payload + * @returns { IEstimateType | undefined } */ - updateEstimateSortOrder = async ( + updateEstimateSwitch = async ( workspaceSlug: string, projectId: string, - payload: TEstimatePointsObject[] - ): Promise => { + payload: IEstimateFormData + ): Promise => { try { if (!this.id || !payload) return; - const estimatePoints = await this.service.updateEstimate(workspaceSlug, projectId, this.id, { - estimate_points: payload, - }); - if (estimatePoints?.points && estimatePoints?.points.length > 0) { + const estimate = await this.service.updateEstimate(workspaceSlug, projectId, this.id, payload); + if (estimate) { runInAction(() => { - estimatePoints?.points.map((estimatePoint) => { - if (estimatePoint.id) - set(this.estimatePoints, [estimatePoint.id], new EstimatePoint(this.store, this.data, estimatePoint)); - }); + this.name = estimate?.name; + this.type = estimate?.type; + estimate?.points && + estimate?.points.map((estimatePoint) => { + if (estimatePoint.id) + this.estimatePoints?.[estimatePoint.id]?.updateEstimatePointObject({ + key: estimatePoint.key, + value: estimatePoint.value, + }); + }); }); } + + return estimate; } catch (error) { throw error; }