diff --git a/web/components/estimates/create/stage-two.tsx b/web/components/estimates/create/stage-two.tsx index f4856fac5..308cd6027 100644 --- a/web/components/estimates/create/stage-two.tsx +++ b/web/components/estimates/create/stage-two.tsx @@ -5,7 +5,7 @@ import { Button, Sortable } from "@plane/ui"; // components import { EstimatePointItem } from "@/components/estimates"; // constants -import { EEstimateSystem, EEstimateUpdateStages, ESTIMATE_SYSTEMS } from "@/constants/estimates"; +import { EEstimateSystem, EEstimateUpdateStages, ESTIMATE_SYSTEMS, maxEstimatesCount } from "@/constants/estimates"; type TEstimateCreateStageTwo = { estimateSystem: EEstimateSystem; @@ -17,7 +17,6 @@ export const EstimateCreateStageTwo: FC = (props) => { const { estimateSystem, estimatePoints, handleEstimatePoints } = props; const currentEstimateSystem = ESTIMATE_SYSTEMS[estimateSystem] || undefined; - const maxEstimatesCount = 11; const addNewEstimationPoint = () => { const currentEstimationPoints = estimatePoints; @@ -60,6 +59,7 @@ export const EstimateCreateStageTwo: FC = (props) => { data={estimatePoints} render={(value: TEstimatePointsObject, index: number) => ( editEstimationPoint(index, value)} diff --git a/web/components/estimates/points/estimate-point-item.tsx b/web/components/estimates/points/estimate-point-item.tsx index a033a84c3..6f40cb9b4 100644 --- a/web/components/estimates/points/estimate-point-item.tsx +++ b/web/components/estimates/points/estimate-point-item.tsx @@ -1,67 +1,83 @@ import { FC, Fragment, useEffect, useRef, useState } from "react"; +import { observer } from "mobx-react"; import { Check, GripVertical, MoveRight, Pencil, Trash2, X } from "lucide-react"; import { Select } from "@headlessui/react"; import { TEstimatePointsObject } from "@plane/types"; -import { Draggable } from "@plane/ui"; +import { Draggable, Spinner } from "@plane/ui"; // constants import { EEstimateUpdateStages } from "@/constants/estimates"; -// components -import { InlineEdit } from "./inline-editable"; +// helpers +import { cn } from "@/helpers/common.helper"; +import { useEstimate } from "@/hooks/store"; type TEstimatePointItem = { + estimateId: string | undefined; mode: EEstimateUpdateStages; item: TEstimatePointsObject; editItem: (value: string) => void; deleteItem: () => void; }; -const EstimatePointItem: FC = (props) => { +export const EstimatePointItem: FC = observer((props) => { // props - const { mode, item, editItem, deleteItem } = props; + const { estimateId, mode, item, editItem, deleteItem } = props; const { id, key, value } = item; + // hooks + const { asJson: estimate, updateEstimate, deleteEstimate } = useEstimate(estimateId); // ref const inputRef = useRef(null); // states const [inputValue, setInputValue] = useState(undefined); - const [isEditing, setIsEditing] = useState(false); - const [showDeleteUI, setShowDeleteUI] = useState(false); + // handling editing states + const [estimateEditLoader, setEstimateEditLoader] = useState(false); + const [deletedEstimateValue, setDeletedEstimateValue] = useState(undefined); + const [isEstimateEditing, setIsEstimateEditing] = useState(false); + const [isEstimateDeleting, setIsEstimateDeleting] = useState(false); useEffect(() => { - if (inputValue === undefined) setInputValue(value); + if (inputValue === undefined || inputValue != value) setInputValue(value); }, [value, inputValue]); - const handleSave = () => { - if (id) { - // Make the api call to save the estimate point - // Show a spinner - setIsEditing(false); - } + const handleCreateEdit = (value: string) => { + setInputValue(value); + editItem(value); }; - const handleEdit = (value: string) => { + const handleEdit = async () => { if (id) { - setIsEditing(true); - setTimeout(() => { - inputRef.current?.focus(); - inputRef.current?.select(); - }); + try { + setEstimateEditLoader(true); + await updateEstimate({ estimate_points: [{ id: id, key: key, value: value }] }); + setIsEstimateEditing(false); + setEstimateEditLoader(false); + } catch (error) { + setEstimateEditLoader(false); + } } else { - setInputValue(value); - editItem(value); + if (inputValue) editItem(inputValue); } }; - const handleDelete = () => { + const handleDelete = async () => { if (id) { - setShowDeleteUI(true); + try { + setEstimateEditLoader(true); + await deleteEstimate(deletedEstimateValue); + setIsEstimateDeleting(false); + setEstimateEditLoader(false); + } catch (error) { + setEstimateEditLoader(false); + } } else { deleteItem(); } }; + const selectDropdownOptions = estimate && estimate?.points ? estimate?.points.filter((point) => point.id !== id) : []; + return ( - {mode === EEstimateUpdateStages.CREATE && ( + {!id && (
@@ -70,8 +86,8 @@ const EstimatePointItem: FC = (props) => { ref={inputRef} type="text" value={inputValue} - onChange={(e) => handleEdit(e.target.value)} - className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5" + onChange={(e) => 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" />
= (props) => {
)} - {mode === EEstimateUpdateStages.EDIT && ( + {id && ( <> -
-
- + {mode === EEstimateUpdateStages.EDIT && ( + <> + {!isEstimateEditing && !isEstimateDeleting && ( +
+
+ +
+
setIsEstimateEditing(true)}> + {value} +
+
setIsEstimateEditing(true)} + > + +
+
setIsEstimateDeleting(true)} + > + +
+
+ )} + + {(isEstimateEditing || isEstimateDeleting) && ( +
+
+
+ setInputValue(e.target.value)} + className={cn( + "flex-grow 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} + /> +
+ {isEstimateDeleting && } + {isEstimateDeleting && ( +
+ +
+ )} +
+ {estimateEditLoader ? ( +
+ +
+ ) : ( +
(isEstimateEditing ? handleEdit() : handleDelete())} + > + {isEstimateEditing ? : } +
+ )} + +
(isEstimateEditing ? setIsEstimateEditing(false) : setIsEstimateDeleting(false))} + > + +
+
+ )} + + )} + + {mode === EEstimateUpdateStages.SWITCH && ( +
+
+ setInputValue(e.target.value)} + className="flex-grow border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 bg-custom-background-90 w-full" + disabled + /> +
+ +
+ setInputValue(e.target.value)} + className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full" + /> +
- setInputValue(e.target.value)} - className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5" - /> -
- -
-
- -
-
+ )} )} - - {mode === EEstimateUpdateStages.SWITCH && ( -
-
- -
- setInputValue(e.target.value)} - className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5" - /> -
- -
-
- -
-
- )} - - {/*
- - {}} - className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5" - /> - - - - -
*/} - - {/* {isEditing && ( -
- {}} - className="border rounded-md border-custom-border-300 p-3 flex-grow" - ref={inputRef} - /> -
-
- - setIsEditing(false)} /> -
-
-
- )} - - {!isEditing && ( -
-
- - {!showDeleteUI ? : value} - {showDeleteUI && ( - - - - - setShowDeleteUI(false)} /> - - )} -
-
- - {!showDeleteUI && } -
-
- )} */} ); -}; - -export { EstimatePointItem }; +}); diff --git a/web/components/estimates/update/modal.tsx b/web/components/estimates/update/modal.tsx index df35ff13e..ce0c9bf1c 100644 --- a/web/components/estimates/update/modal.tsx +++ b/web/components/estimates/update/modal.tsx @@ -1,5 +1,4 @@ import { FC, useEffect, useMemo, useState } from "react"; -import cloneDeep from "lodash/cloneDeep"; import { observer } from "mobx-react"; import { ChevronLeft } from "lucide-react"; import { IEstimateFormData, TEstimatePointsObject, TEstimateUpdateStageKeys, TEstimateSystemKeys } from "@plane/types"; @@ -46,10 +45,7 @@ export const UpdateEstimateModal: FC = observer((props) => } }; - const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => { - const points = cloneDeep(newPoints); - setEstimatePoints(points); - }; + const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints); useEffect(() => { if (!isOpen) { @@ -109,8 +105,6 @@ export const UpdateEstimateModal: FC = observer((props) => } }; - console.log("estimateStage", estimateEditType); - return (
diff --git a/web/components/estimates/update/stage-two.tsx b/web/components/estimates/update/stage-two.tsx index fe42fa062..4d6f61ebe 100644 --- a/web/components/estimates/update/stage-two.tsx +++ b/web/components/estimates/update/stage-two.tsx @@ -4,6 +4,8 @@ import { IEstimate, TEstimatePointsObject, TEstimateUpdateStageKeys } from "@pla import { Button, Sortable } from "@plane/ui"; // components import { EstimatePointItem } from "@/components/estimates"; +// constants +import { EEstimateUpdateStages, maxEstimatesCount } from "@/constants/estimates"; type TEstimateUpdateStageTwo = { estimate: IEstimate; @@ -19,7 +21,6 @@ export const EstimateUpdateStageTwo: FC = (props) => { const addNewEstimationPoint = () => { const currentEstimationPoints = estimatePoints; - const newEstimationPoint: TEstimatePointsObject = { key: currentEstimationPoints.length + 1, value: "0", @@ -27,32 +28,61 @@ export const EstimateUpdateStageTwo: FC = (props) => { handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]); }; - const deleteEstimationPoint = (index: number) => { + const editEstimationPoint = (index: number, value: string) => { const newEstimationPoints = estimatePoints; - newEstimationPoints.splice(index, 1); + newEstimationPoints[index].value = value; handleEstimatePoints(newEstimationPoints); }; - const updatedSortedKeys = (updatedEstimatePoints: TEstimatePointsObject[]) => - updatedEstimatePoints.map((item, index) => ({ + const deleteEstimationPoint = (index: number) => { + let newEstimationPoints = estimatePoints; + newEstimationPoints.splice(index, 1); + newEstimationPoints = newEstimationPoints.map((item, index) => ({ + ...item, + key: index + 1, + })); + handleEstimatePoints(newEstimationPoints); + }; + + const updatedSortedKeys = (updatedEstimatePoints: TEstimatePointsObject[]) => { + const sortedEstimatePoints = updatedEstimatePoints.map((item, index) => ({ ...item, key: index + 1, })) as TEstimatePointsObject[]; + return sortedEstimatePoints; + }; + if (!estimateEditType) return <>; return ( -
- ( - deleteEstimationPoint(index)} /> +
+
+ {estimateEditType === EEstimateUpdateStages.SWITCH ? "Estimate type switching" : currentEstimateSystem?.type} +
+
+ ( + editEstimationPoint(index, value)} + deleteItem={() => deleteEstimationPoint(index)} + /> + )} + onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))} + keyExtractor={(item: TEstimatePointsObject) => item?.id?.toString() || item.value.toString()} + /> + {estimateEditType === EEstimateUpdateStages.EDIT && ( + <> + {estimatePoints && estimatePoints.length <= maxEstimatesCount && ( + + )} + )} - onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))} - keyExtractor={(item: TEstimatePointsObject) => item?.id?.toString() || item.value.toString()} - /> - - +
); }; diff --git a/web/constants/estimates.ts b/web/constants/estimates.ts index e552f8d9f..668d4061a 100644 --- a/web/constants/estimates.ts +++ b/web/constants/estimates.ts @@ -1,3 +1,6 @@ +// types +import { TEstimateSystems } from "@plane/types"; + export enum EEstimateSystem { POINTS = "points", CATEGORIES = "categories", @@ -10,8 +13,7 @@ export enum EEstimateUpdateStages { SWITCH = "switch", } -// types -import { TEstimateSystems } from "@plane/types"; +export const maxEstimatesCount = 11; export const ESTIMATE_SYSTEMS: TEstimateSystems = { points: { diff --git a/web/store/estimates/estimate.ts b/web/store/estimates/estimate.ts index ff47a7ca8..fb0084ff8 100644 --- a/web/store/estimates/estimate.ts +++ b/web/store/estimates/estimate.ts @@ -28,11 +28,11 @@ export interface IEstimate extends IEstimateType { estimatePoints: Record; // computed asJson: IEstimateType; - EstimatePointIds: string[] | undefined; + estimatePointIds: string[] | undefined; estimatePointById: (estimateId: string) => IEstimatePointType | undefined; // actions updateEstimate: (payload: IEstimateFormData) => Promise; - deleteEstimate: (estimatePointId: string) => Promise; + deleteEstimate: (estimatePointId: string | undefined) => Promise; } export class Estimate implements IEstimate { @@ -80,7 +80,7 @@ export class Estimate implements IEstimate { estimatePoints: observable, // computed asJson: computed, - EstimatePointIds: computed, + estimatePointIds: computed, // actions updateEstimate: action, deleteEstimate: action, @@ -126,7 +126,7 @@ export class Estimate implements IEstimate { }; } - get EstimatePointIds() { + get estimatePointIds() { const { estimatePoints } = this; if (!estimatePoints) return undefined; @@ -159,7 +159,7 @@ export class Estimate implements IEstimate { } }; - deleteEstimate = async (estimatePointId: string) => { + deleteEstimate = async (estimatePointId: string | undefined) => { try { const { workspaceSlug, projectId } = this.store.router; if (!workspaceSlug || !projectId || !estimatePointId) return;