From 16fa9c9baabcc6f43f1b5e85588ff539375760cb Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Mon, 3 Jun 2024 17:06:34 +0530 Subject: [PATCH] fix: updated and handled the estimate points --- packages/types/src/estimate.d.ts | 2 +- web/components/estimates/create/modal.tsx | 13 ++- web/components/estimates/create/stage-one.tsx | 6 +- web/components/estimates/delete/modal.tsx | 110 +++++------------- web/components/estimates/ee-banner.tsx | 25 ++++ .../estimates/estimate-list-item-buttons.tsx | 14 ++- .../estimates/estimate-list-item.tsx | 4 +- web/components/estimates/estimate-list.tsx | 4 +- web/components/estimates/index.ts | 6 +- web/components/estimates/root.tsx | 29 +++-- web/components/estimates/update/stage-one.tsx | 6 +- web/constants/estimates.ts | 10 +- web/services/project/estimate.service.ts | 2 +- web/store/estimates/project-estimate.store.ts | 27 +++-- 14 files changed, 129 insertions(+), 129 deletions(-) create mode 100644 web/components/estimates/ee-banner.tsx diff --git a/packages/types/src/estimate.d.ts b/packages/types/src/estimate.d.ts index 049165249..9bad7e260 100644 --- a/packages/types/src/estimate.d.ts +++ b/packages/types/src/estimate.d.ts @@ -63,7 +63,7 @@ export type TEstimateSystem = { name: string; templates: Record; is_available: boolean; - is_active: boolean; + is_ee: boolean; }; export type TEstimateSystems = { diff --git a/web/components/estimates/create/modal.tsx b/web/components/estimates/create/modal.tsx index 7d1106d00..38b3a81e9 100644 --- a/web/components/estimates/create/modal.tsx +++ b/web/components/estimates/create/modal.tsx @@ -26,6 +26,7 @@ export const CreateEstimateModal: FC = observer((props) => // states const [estimateSystem, setEstimateSystem] = useState(EEstimateSystem.CATEGORIES); const [estimatePoints, setEstimatePoints] = useState(undefined); + const [buttonLoader, setButtonLoader] = useState(false); const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints); @@ -39,7 +40,7 @@ export const CreateEstimateModal: FC = observer((props) => const handleCreateEstimate = async () => { try { if (!workspaceSlug || !projectId || !estimatePoints) return; - + setButtonLoader(true); const payload: IEstimateFormData = { estimate: { name: ESTIMATE_SYSTEMS[estimateSystem]?.name, @@ -50,13 +51,15 @@ export const CreateEstimateModal: FC = observer((props) => }; await createEstimate(workspaceSlug, projectId, payload); + setButtonLoader(false); setToast({ type: TOAST_TYPE.SUCCESS, title: "Estimate created", - message: "Created and Enabled successfully", + message: "A new estimate has been added in your project.", }); handleClose(); } catch (error) { + setButtonLoader(false); setToast({ type: TOAST_TYPE.ERROR, title: "Estimate creation failed", @@ -116,12 +119,12 @@ export const CreateEstimateModal: FC = observer((props) =>
- {estimatePoints && ( - )}
diff --git a/web/components/estimates/create/stage-one.tsx b/web/components/estimates/create/stage-one.tsx index 87e96192a..8f12675e7 100644 --- a/web/components/estimates/create/stage-one.tsx +++ b/web/components/estimates/create/stage-one.tsx @@ -31,7 +31,7 @@ export const EstimateCreateStageOne: FC = (props) => { - ) : ESTIMATE_SYSTEMS[currentSystem]?.is_active ? ( + ) : ESTIMATE_SYSTEMS[currentSystem]?.is_ee ? (
{ESTIMATE_SYSTEMS[currentSystem]?.name} @@ -42,7 +42,7 @@ export const EstimateCreateStageOne: FC = (props) => {
{ESTIMATE_SYSTEMS[currentSystem]?.name}
), value: system, - disabled: !ESTIMATE_SYSTEMS[currentSystem]?.is_available || ESTIMATE_SYSTEMS[currentSystem]?.is_active, + disabled: !ESTIMATE_SYSTEMS[currentSystem]?.is_available || ESTIMATE_SYSTEMS[currentSystem]?.is_ee, }; })} label="Choose an estimate system" @@ -55,7 +55,7 @@ export const EstimateCreateStageOne: FC = (props) => { />
- {ESTIMATE_SYSTEMS[estimateSystem]?.is_available && !ESTIMATE_SYSTEMS[estimateSystem]?.is_active && ( + {ESTIMATE_SYSTEMS[estimateSystem]?.is_available && !ESTIMATE_SYSTEMS[estimateSystem]?.is_ee && ( <>
Start from scratch
diff --git a/web/components/estimates/delete/modal.tsx b/web/components/estimates/delete/modal.tsx index 3692dc9f3..83f0f49cc 100644 --- a/web/components/estimates/delete/modal.tsx +++ b/web/components/estimates/delete/modal.tsx @@ -1,129 +1,79 @@ -import { FC, useEffect, useMemo, useState } from "react"; +import { FC, useState } from "react"; import { observer } from "mobx-react"; -import { ChevronLeft } from "lucide-react"; -import { IEstimateFormData, TEstimateSystemKeys, TEstimatePointsObject } from "@plane/types"; import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // components import { EModalPosition, EModalWidth, ModalCore } from "@/components/core"; -import { EstimateCreateStageOne, EstimatePointCreateRoot } from "@/components/estimates"; -// constants -import { EEstimateSystem, ESTIMATE_SYSTEMS } from "@/constants/estimates"; // hooks -import { useProjectEstimates } from "@/hooks/store"; +import { useEstimate, useProject, useProjectEstimates } from "@/hooks/store"; type TDeleteEstimateModal = { workspaceSlug: string; projectId: string; + estimateId: string | undefined; isOpen: boolean; handleClose: () => void; }; export const DeleteEstimateModal: FC = observer((props) => { // props - const { workspaceSlug, projectId, isOpen, handleClose } = props; + const { workspaceSlug, projectId, estimateId, isOpen, handleClose } = props; // hooks - const { createEstimate } = useProjectEstimates(); + const { areEstimateEnabledByProjectId, deleteEstimate } = useProjectEstimates(); + const { asJson: estimate } = useEstimate(estimateId); + const { updateProject } = useProject(); // states - const [estimateSystem, setEstimateSystem] = useState(EEstimateSystem.CATEGORIES); - const [estimatePoints, setEstimatePoints] = useState(undefined); + const [buttonLoader, setButtonLoader] = useState(false); - const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints); - - useEffect(() => { - if (isOpen) { - setEstimateSystem(EEstimateSystem.CATEGORIES); - setEstimatePoints(undefined); - } - }, [isOpen]); - - const handleCreateEstimate = async () => { + const handleDeleteEstimate = async () => { try { - if (!workspaceSlug || !projectId || !estimatePoints) return; - - const payload: IEstimateFormData = { - estimate: { - name: ESTIMATE_SYSTEMS[estimateSystem]?.name, - type: estimateSystem, - last_used: true, - }, - estimate_points: estimatePoints, - }; - await createEstimate(workspaceSlug, projectId, payload); + if (!workspaceSlug || !projectId || !estimateId) return; + setButtonLoader(true); + await deleteEstimate(workspaceSlug, projectId, estimateId); + if (areEstimateEnabledByProjectId(projectId)) { + await updateProject(workspaceSlug, projectId, { estimate: null }); + } + setButtonLoader(false); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Estimate created", - message: "Created and Enabled successfully", + title: "Estimate deleted", + message: "Estimate has been removed from your project.", }); handleClose(); } catch (error) { + setButtonLoader(false); setToast({ type: TOAST_TYPE.ERROR, title: "Estimate creation failed", - message: "We were unable to create the new estimate, please try again.", + message: "We were unable to delete the estimate, please try again.", }); } }; - // derived values - const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]); - return (
{/* heading */}
-
- {estimatePoints && ( -
{ - setEstimateSystem(EEstimateSystem.CATEGORIES); - handleUpdatePoints(undefined); - }} - className="flex-shrink-0 cursor-pointer w-5 h-5 flex justify-center items-center" - > - -
- )} -
New Estimate System
-
-
Step {renderEstimateStepsCount} of 2
+
Delete Estimate System
{/* estimate steps */}
- {!estimatePoints && ( - - handleUpdatePoints(ESTIMATE_SYSTEMS[estimateSystem].templates[templateType].values) - } - /> - )} - {estimatePoints && ( - <> - - - )} +
+ Deleting the estimate {estimate?.name} +  system will remove it from all issues permanently. This action cannot be undone. If you add estimates + again, you will need to update all the issues. +
- - {estimatePoints && ( - - )} +
diff --git a/web/components/estimates/ee-banner.tsx b/web/components/estimates/ee-banner.tsx new file mode 100644 index 000000000..ff43be0ee --- /dev/null +++ b/web/components/estimates/ee-banner.tsx @@ -0,0 +1,25 @@ +import { FC } from "react"; +import { Button } from "@plane/ui"; + +export const EstimateEEBanner: FC = (props) => { + const {} = props; + + return ( +
+
+
Estimate issues better with points
+
+ Use points to estimate scope of work better, monitor capacity, track the burn-down report for your project. +
+
+ +
Talk custom pricing
+
+
+ +
Image
+
+ ); +}; diff --git a/web/components/estimates/estimate-list-item-buttons.tsx b/web/components/estimates/estimate-list-item-buttons.tsx index eec5514d7..d2da059ee 100644 --- a/web/components/estimates/estimate-list-item-buttons.tsx +++ b/web/components/estimates/estimate-list-item-buttons.tsx @@ -1,3 +1,4 @@ +import { FC } from "react"; import { observer } from "mobx-react"; import { Pen, Trash } from "lucide-react"; @@ -7,12 +8,15 @@ type TEstimateListItem = { isEstimateEnabled: boolean; isEditable: boolean; onEditClick?: (estimateId: string) => void; + onDeleteClick?: (estimateId: string) => void; }; export const EstimateListItemButtons: FC = observer((props) => { - const { estimateId, isAdmin, isEditable, onEditClick } = props; - return isAdmin && isEditable ? ( -
+ const { estimateId, isAdmin, isEditable, onEditClick, onDeleteClick } = props; + if (!isAdmin || !isEditable) return <>; + + return ( +
- ) : null; + ); }); diff --git a/web/components/estimates/estimate-list-item.tsx b/web/components/estimates/estimate-list-item.tsx index b926656a9..8a27722a2 100644 --- a/web/components/estimates/estimate-list-item.tsx +++ b/web/components/estimates/estimate-list-item.tsx @@ -1,6 +1,5 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { Pen } from "lucide-react"; // helpers import { cn } from "@/helpers/common.helper"; // hooks @@ -13,10 +12,11 @@ type TEstimateListItem = { isEstimateEnabled: boolean; isEditable: boolean; onEditClick?: (estimateId: string) => void; + onDeleteClick?: (estimateId: string) => void; }; export const EstimateListItem: FC = observer((props) => { - const { estimateId, isAdmin, isEstimateEnabled, isEditable, onEditClick } = props; + const { estimateId, isAdmin, isEstimateEnabled, isEditable } = props; // hooks const { estimateById } = useProjectEstimates(); const { estimatePointIds, estimatePointById } = useEstimate(estimateId); diff --git a/web/components/estimates/estimate-list.tsx b/web/components/estimates/estimate-list.tsx index 51e237108..57de5bc8d 100644 --- a/web/components/estimates/estimate-list.tsx +++ b/web/components/estimates/estimate-list.tsx @@ -9,10 +9,11 @@ type TEstimateList = { isEstimateEnabled?: boolean; isEditable?: boolean; onEditClick?: (estimateId: string) => void; + onDeleteClick?: (estimateId: string) => void; }; export const EstimateList: FC = observer((props) => { - const { estimateIds, isAdmin, isEstimateEnabled = false, isEditable = false, onEditClick } = props; + const { estimateIds, isAdmin, isEstimateEnabled = false, isEditable = false, onEditClick, onDeleteClick } = props; if (!estimateIds || estimateIds?.length <= 0) return <>; return ( @@ -26,6 +27,7 @@ export const EstimateList: FC = observer((props) => { isEstimateEnabled={isEstimateEnabled} isEditable={isEditable} onEditClick={onEditClick} + onDeleteClick={onDeleteClick} /> ))}
diff --git a/web/components/estimates/index.ts b/web/components/estimates/index.ts index 531e466af..45572e7f0 100644 --- a/web/components/estimates/index.ts +++ b/web/components/estimates/index.ts @@ -2,6 +2,7 @@ export * from "./root"; export * from "./empty-screen"; export * from "./loader-screen"; +export * from "./ee-banner"; export * from "./estimate-search"; export * from "./estimate-disable-switch"; @@ -9,11 +10,12 @@ export * from "./estimate-disable-switch"; // estimates export * from "./estimate-list"; export * from "./estimate-list-item"; +export * from "./estimate-list-item-buttons"; // create export * from "./create"; -// create +// update export * from "./update"; // delete @@ -21,5 +23,3 @@ export * from "./delete"; // estimate points export * from "./points"; - -export * from "./estimate-list-item-buttons"; diff --git a/web/components/estimates/root.tsx b/web/components/estimates/root.tsx index c23d4746b..702a403df 100644 --- a/web/components/estimates/root.tsx +++ b/web/components/estimates/root.tsx @@ -8,7 +8,9 @@ import { EstimateDisableSwitch, CreateEstimateModal, UpdateEstimateModal, + DeleteEstimateModal, EstimateList, + EstimateEEBanner, } from "@/components/estimates"; // hooks import { useProject, useProjectEstimates } from "@/hooks/store"; @@ -27,6 +29,7 @@ export const EstimateRoot: FC = observer((props) => { // states const [isEstimateCreateModalOpen, setIsEstimateCreateModalOpen] = useState(false); const [estimateToUpdate, setEstimateToUpdate] = useState(); + const [estimateToDelete, setEstimateToDelete] = useState(); const { isLoading: isSWRLoading } = useSWR( workspaceSlug && projectId ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}` : null, @@ -38,7 +41,7 @@ export const EstimateRoot: FC = observer((props) => { {loader === "init-loader" || isSWRLoading ? ( ) : ( -
+
{/* header */}
Estimates @@ -46,7 +49,7 @@ export const EstimateRoot: FC = observer((props) => { {/* current active estimate section */} {currentActiveEstimateId ? ( -
+
{/* estimates activated deactivated section */}
@@ -64,6 +67,7 @@ export const EstimateRoot: FC = observer((props) => { isEstimateEnabled={Boolean(currentProjectDetails?.estimate)} isEditable onEditClick={(estimateId: string) => setEstimateToUpdate(estimateId)} + onDeleteClick={(estimateId: string) => setEstimateToDelete(estimateId)} />
) : ( @@ -72,7 +76,7 @@ export const EstimateRoot: FC = observer((props) => { {/* archived estimates section */} {archivedEstimateIds && archivedEstimateIds.length > 0 && ( -
+

Archived estimates

@@ -89,25 +93,28 @@ export const EstimateRoot: FC = observer((props) => {

)} + {/* */} + {/* CRUD modals */} { - setIsEstimateCreateModalOpen(false); - setEstimateToUpdate(undefined); - }} + handleClose={() => setIsEstimateCreateModalOpen(false)} /> { - setIsEstimateCreateModalOpen(false); - setEstimateToUpdate(undefined); - }} + handleClose={() => setEstimateToUpdate(undefined)} + /> + setEstimateToDelete(undefined)} />
); diff --git a/web/components/estimates/update/stage-one.tsx b/web/components/estimates/update/stage-one.tsx index ef2d18fb2..d0bd1b4a8 100644 --- a/web/components/estimates/update/stage-one.tsx +++ b/web/components/estimates/update/stage-one.tsx @@ -22,13 +22,13 @@ export const EstimateUpdateStageOne: FC = (props) => { key={stage.key} className={cn( "border border-custom-border-300 cursor-pointer space-y-1 p-3 rounded transition-colors", - stage?.is_active ? `bg-custom-background-90` : `hover:bg-custom-background-90` + stage?.is_ee ? `bg-custom-background-90` : `hover:bg-custom-background-90` )} - onClick={() => !stage?.is_active && handleEstimateEditType(stage.key)} + onClick={() => !stage?.is_ee && handleEstimateEditType(stage.key)} >

{stage.title} - {stage?.is_active && ( + {stage?.is_ee && ( diff --git a/web/constants/estimates.ts b/web/constants/estimates.ts index 2cb7215fc..5a3729503 100644 --- a/web/constants/estimates.ts +++ b/web/constants/estimates.ts @@ -49,7 +49,7 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = { }, }, is_available: true, - is_active: false, + is_ee: false, }, points: { name: "Points", @@ -102,7 +102,7 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = { }, }, is_available: true, - is_active: true, + is_ee: false, }, time: { name: "Time", @@ -124,7 +124,7 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = { }, }, is_available: false, - is_active: true, + is_ee: true, }, }; @@ -133,12 +133,12 @@ export const ESTIMATE_OPTIONS_STAGE_ONE = [ key: EEstimateUpdateStages.EDIT, title: "Add, update or remove estimates", description: "Manage current system either adding, updating or removing the points or categories.", - is_active: false, + is_ee: true, }, { key: EEstimateUpdateStages.SWITCH, title: "Change estimate type", description: "Convert your points system to categories system and vice versa.", - is_active: true, + is_ee: true, }, ]; diff --git a/web/services/project/estimate.service.ts b/web/services/project/estimate.service.ts index 1d0d38720..cbb81c43f 100644 --- a/web/services/project/estimate.service.ts +++ b/web/services/project/estimate.service.ts @@ -73,7 +73,7 @@ class EstimateService extends APIService { } } - async deleteEstimate(workspaceSlug: string, projectId: string, estimateId: string): Promise { + async deleteEstimate(workspaceSlug: string, projectId: string, estimateId: string): Promise { try { await this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`); } catch (error) { diff --git a/web/store/estimates/project-estimate.store.ts b/web/store/estimates/project-estimate.store.ts index f935b8cc5..1ac77dce3 100644 --- a/web/store/estimates/project-estimate.store.ts +++ b/web/store/estimates/project-estimate.store.ts @@ -1,5 +1,6 @@ import orderBy from "lodash/orderBy"; 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"; @@ -40,6 +41,7 @@ export interface IProjectEstimateStore { projectId: string, data: IEstimateFormData ) => Promise; + deleteEstimate: (workspaceSlug: string, projectId: string, estimateId: string) => Promise; } export class ProjectEstimateStore implements IProjectEstimateStore { @@ -62,6 +64,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore { getProjectEstimates: action, getEstimateById: action, createEstimate: action, + deleteEstimate: action, }); } @@ -249,9 +252,10 @@ export class ProjectEstimateStore implements IProjectEstimateStore { const estimate = await estimateService.createEstimate(workspaceSlug, projectId, payload); if (estimate) { - await this.store.projectRoot.project.updateProject(workspaceSlug, projectId, { - estimate: estimate.id, - }); + // update estimate_id in current project + // 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)); }); @@ -268,16 +272,21 @@ export class ProjectEstimateStore implements IProjectEstimateStore { }; /** - * @description deletes the given estimate for the given project + * @description delete the estimate for a project * @param workspaceSlug * @param projectId * @param estimateId */ deleteEstimate = async (workspaceSlug: string, projectId: string, estimateId: string) => { - await estimateService.deleteEstimate(workspaceSlug, projectId, estimateId).then(() => { - runInAction(() => { - delete this.estimates[estimateId]; - }); - }); + try { + await estimateService.deleteEstimate(workspaceSlug, projectId, estimateId); + runInAction(() => estimateId && unset(this.estimates, [estimateId])); + } catch (error) { + this.error = { + status: "error", + message: "Error deleting estimate", + }; + throw error; + } }; }