[WEB-522] chore: estimate restructure and handled error while creating estimates (#4817)

* dev:updated estimate UI

* dev: updated error messages in estimate point create
This commit is contained in:
guru_sainath 2024-06-14 13:23:58 +05:30 committed by GitHub
parent 9145234a6c
commit b3626d815f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 170 additions and 73 deletions

View File

@ -75,3 +75,13 @@ export type TEstimateUpdateStageKeys =
| EEstimateUpdateStages.CREATE | EEstimateUpdateStages.CREATE
| EEstimateUpdateStages.EDIT | EEstimateUpdateStages.EDIT
| EEstimateUpdateStages.SWITCH; | EEstimateUpdateStages.SWITCH;
export type TEstimateTypeErrorObject = {
oldValue: string;
newValue: string;
message: string | undefined;
};
export type TEstimateTypeError =
| Record<number, TEstimateTypeErrorObject>
| undefined;

View File

@ -3,7 +3,7 @@
import { FC, useEffect, useMemo, useState } from "react"; import { FC, useEffect, useMemo, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { ChevronLeft } from "lucide-react"; import { ChevronLeft } from "lucide-react";
import { IEstimateFormData, TEstimateSystemKeys, TEstimatePointsObject } from "@plane/types"; import { IEstimateFormData, TEstimateSystemKeys, TEstimatePointsObject, TEstimateTypeError } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core"; import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
@ -28,25 +28,59 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
// states // states
const [estimateSystem, setEstimateSystem] = useState<TEstimateSystemKeys>(EEstimateSystem.POINTS); const [estimateSystem, setEstimateSystem] = useState<TEstimateSystemKeys>(EEstimateSystem.POINTS);
const [estimatePoints, setEstimatePoints] = useState<TEstimatePointsObject[] | undefined>(undefined); const [estimatePoints, setEstimatePoints] = useState<TEstimatePointsObject[] | undefined>(undefined);
const [estimatePointCreate, setEstimatePointCreate] = useState<TEstimatePointsObject[] | undefined>(undefined); const [estimatePointError, setEstimatePointError] = useState<TEstimateTypeError>(undefined);
const [estimatePointCreateError, setEstimatePointCreateError] = useState<number[]>([]);
const [buttonLoader, setButtonLoader] = useState(false); const [buttonLoader, setButtonLoader] = useState(false);
const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints); const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints);
const handleEstimatePointError = (
key: number,
oldValue: string,
newValue: string,
message: string | undefined,
mode: "add" | "delete" = "add"
) => {
setEstimatePointError((prev) => {
if (mode === "add") {
return { ...prev, [key]: { oldValue, newValue, message } };
} else {
const newError = { ...prev };
delete newError[key];
return newError;
}
});
};
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
setEstimateSystem(EEstimateSystem.POINTS); setEstimateSystem(EEstimateSystem.POINTS);
setEstimatePoints(undefined); setEstimatePoints(undefined);
setEstimatePointError([]);
} }
}, [isOpen]); }, [isOpen]);
const validateEstimatePointError = () => {
let estimateError = false;
if (!estimatePointError) return estimateError;
Object.keys(estimatePointError || {}).forEach((key) => {
const currentKey = key as unknown as number;
if (
estimatePointError[currentKey]?.oldValue != estimatePointError[currentKey]?.newValue ||
estimatePointError[currentKey]?.newValue === "" ||
estimatePointError[currentKey]?.message
) {
estimateError = true;
}
});
return estimateError;
};
const handleCreateEstimate = async () => { const handleCreateEstimate = async () => {
setEstimatePointCreateError([]); if (!validateEstimatePointError()) {
if (estimatePointCreate === undefined || estimatePointCreate?.length === 0) {
try { try {
if (!workspaceSlug || !projectId || !estimatePoints) return; if (!workspaceSlug || !projectId || !estimatePoints) return;
setButtonLoader(true); setButtonLoader(true);
const payload: IEstimateFormData = { const payload: IEstimateFormData = {
estimate: { estimate: {
@ -57,7 +91,6 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
estimate_points: estimatePoints, estimate_points: estimatePoints,
}; };
await createEstimate(workspaceSlug, projectId, payload); await createEstimate(workspaceSlug, projectId, payload);
setButtonLoader(false); setButtonLoader(false);
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
@ -74,12 +107,32 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
}); });
} }
} else { } else {
setEstimatePointCreateError(estimatePointCreate.map((point) => point.key)); setEstimatePointError((prev) => {
const newError = { ...prev };
Object.keys(newError || {}).forEach((key) => {
const currentKey = key as unknown as number;
if (
newError[currentKey]?.newValue != "" &&
newError[currentKey]?.oldValue === newError[currentKey]?.newValue
) {
delete newError[currentKey];
} else {
newError[currentKey].message =
newError[currentKey].message ||
"Estimate point can't be empty. Enter a value in each field or remove those you don't have values for.";
}
});
return newError;
});
} }
}; };
// derived values // derived values
const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]); const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]);
// const isEstimatePointError = useMemo(() => {
// if (!estimatePointError) return false;
// return Object.keys(estimatePointError).length > 0;
// }, [estimatePointError]);
return ( return (
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}> <ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
@ -122,20 +175,16 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
estimateType={estimateSystem} estimateType={estimateSystem}
estimatePoints={estimatePoints} estimatePoints={estimatePoints}
setEstimatePoints={setEstimatePoints} setEstimatePoints={setEstimatePoints}
estimatePointCreate={estimatePointCreate} estimatePointError={estimatePointError}
setEstimatePointCreate={(value) => { handleEstimatePointError={handleEstimatePointError}
setEstimatePointCreateError([]);
setEstimatePointCreate(value);
}}
estimatePointCreateError={estimatePointCreateError}
/> />
)} )}
{estimatePointCreateError.length > 0 && ( {/* {isEstimatePointError && (
<div className="pt-5 text-sm text-red-500"> <div className="pt-5 text-sm text-red-500">
Estimate points can&apos;t be empty. Enter a value in each field or remove those you don&apos;t have Estimate points can&apos;t be empty. Enter a value in each field or remove those you don&apos;t have
values for. values for.
</div> </div>
)} )} */}
</div> </div>
<div className="relative flex justify-end items-center gap-3 px-5 pt-5 border-t border-custom-border-200"> <div className="relative flex justify-end items-center gap-3 px-5 pt-5 border-t border-custom-border-200">

View File

@ -1,9 +1,9 @@
"use client"; "use client";
import { Dispatch, FC, SetStateAction, useCallback } from "react"; import { Dispatch, FC, SetStateAction, useCallback, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types"; import { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeError } from "@plane/types";
import { Button, Sortable } from "@plane/ui"; import { Button, Sortable } from "@plane/ui";
// components // components
import { EstimatePointCreate, EstimatePointItemPreview } from "@/components/estimates/points"; import { EstimatePointCreate, EstimatePointItemPreview } from "@/components/estimates/points";
@ -17,9 +17,14 @@ type TEstimatePointCreateRoot = {
estimateType: TEstimateSystemKeys; estimateType: TEstimateSystemKeys;
estimatePoints: TEstimatePointsObject[]; estimatePoints: TEstimatePointsObject[];
setEstimatePoints: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>; setEstimatePoints: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>;
estimatePointCreate: TEstimatePointsObject[] | undefined; estimatePointError?: TEstimateTypeError;
setEstimatePointCreate: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>; handleEstimatePointError?: (
estimatePointCreateError: number[]; key: number,
oldValue: string,
newValue: string,
message: string | undefined,
mode: "add" | "delete"
) => void;
}; };
export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((props) => { export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((props) => {
@ -31,10 +36,11 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
estimateType, estimateType,
estimatePoints, estimatePoints,
setEstimatePoints, setEstimatePoints,
estimatePointCreate, estimatePointError,
setEstimatePointCreate, handleEstimatePointError,
estimatePointCreateError,
} = props; } = props;
// states
const [estimatePointCreate, setEstimatePointCreate] = useState<TEstimatePointsObject[] | undefined>(undefined);
const handleEstimatePoint = useCallback( const handleEstimatePoint = useCallback(
(mode: "add" | "remove" | "update", value: TEstimatePointsObject) => { (mode: "add" | "remove" | "update", value: TEstimatePointsObject) => {
@ -90,11 +96,13 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
const handleCreate = () => { const handleCreate = () => {
if (estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= estimateCount.max - 1) { if (estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= estimateCount.max - 1) {
const currentKey = estimatePoints.length + (estimatePointCreate?.length || 0) + 1;
handleEstimatePointCreate("add", { handleEstimatePointCreate("add", {
id: undefined, id: undefined,
key: estimatePoints.length + (estimatePointCreate?.length || 0) + 1, key: currentKey,
value: "", value: "",
}); });
handleEstimatePointError && handleEstimatePointError(currentKey, "", "", undefined, "add");
} }
}; };
@ -119,6 +127,14 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
handleEstimatePoint("update", { ...value, value: estimatePointValue }) handleEstimatePoint("update", { ...value, value: estimatePointValue })
} }
handleEstimatePointValueRemove={() => handleEstimatePoint("remove", value)} handleEstimatePointValueRemove={() => handleEstimatePoint("remove", value)}
estimatePointError={estimatePointError?.[value.key] || undefined}
handleEstimatePointError={(
newValue: string,
message: string | undefined,
mode: "add" | "delete" = "add"
) =>
handleEstimatePointError && handleEstimatePointError(value.key, value.value, newValue, message, mode)
}
/> />
)} )}
onChange={(data: TEstimatePointsObject[]) => handleDragEstimatePoints(data)} onChange={(data: TEstimatePointsObject[]) => handleDragEstimatePoints(data)}
@ -140,7 +156,11 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
} }
closeCallBack={() => handleEstimatePointCreate("remove", estimatePoint)} closeCallBack={() => handleEstimatePointCreate("remove", estimatePoint)}
handleCreateCallback={() => estimatePointCreate.length === 1 && handleCreate()} handleCreateCallback={() => estimatePointCreate.length === 1 && handleCreate()}
isError={estimatePointCreateError.includes(estimatePoint.key) ? true : false} estimatePointError={estimatePointError?.[estimatePoint.key] || undefined}
handleEstimatePointError={(newValue: string, message: string | undefined, mode: "add" | "delete" = "add") =>
handleEstimatePointError &&
handleEstimatePointError(estimatePoint.key, estimatePoint.value, newValue, message, mode)
}
/> />
))} ))}
{estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= estimateCount.max - 1 && ( {estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= estimateCount.max - 1 && (

View File

@ -1,9 +1,9 @@
"use client"; "use client";
import { FC, useState, FormEvent, useEffect } from "react"; import { FC, useState, FormEvent } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Check, Info, X } from "lucide-react"; import { Check, Info, X } from "lucide-react";
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types"; import { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeErrorObject } from "@plane/types";
import { Spinner, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; import { Spinner, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
@ -22,7 +22,8 @@ type TEstimatePointCreate = {
handleEstimatePointValue?: (estimateValue: string) => void; handleEstimatePointValue?: (estimateValue: string) => void;
closeCallBack: () => void; closeCallBack: () => void;
handleCreateCallback: () => void; handleCreateCallback: () => void;
isError: boolean; estimatePointError?: TEstimateTypeErrorObject | undefined;
handleEstimatePointError?: (newValue: string, message: string | undefined, mode?: "add" | "delete") => void;
}; };
export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) => { export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) => {
@ -35,21 +36,14 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
handleEstimatePointValue, handleEstimatePointValue,
closeCallBack, closeCallBack,
handleCreateCallback, handleCreateCallback,
isError, estimatePointError,
handleEstimatePointError,
} = props; } = props;
// hooks // hooks
const { creteEstimatePoint } = useEstimate(estimateId); const { creteEstimatePoint } = useEstimate(estimateId);
// states // states
const [estimateInputValue, setEstimateInputValue] = useState(""); const [estimateInputValue, setEstimateInputValue] = useState("");
const [loader, setLoader] = useState(false); const [loader, setLoader] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);
useEffect(() => {
if (isError && error === undefined && estimateInputValue.length > 0) {
setError("Confirm this value first or discard it.");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isError]);
const handleSuccess = (value: string) => { const handleSuccess = (value: string) => {
handleEstimatePointValue && handleEstimatePointValue(value); handleEstimatePointValue && handleEstimatePointValue(value);
@ -58,13 +52,14 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
}; };
const handleClose = () => { const handleClose = () => {
handleEstimatePointError && handleEstimatePointError(estimateInputValue, undefined, "delete");
setEstimateInputValue(""); setEstimateInputValue("");
closeCallBack(); closeCallBack();
}; };
const handleEstimateInputValue = (value: string) => { const handleEstimateInputValue = (value: string) => {
setError(undefined);
setEstimateInputValue(value); setEstimateInputValue(value);
handleEstimatePointError && handleEstimatePointError(value, undefined);
}; };
const handleCreate = async (event: FormEvent<HTMLFormElement>) => { const handleCreate = async (event: FormEvent<HTMLFormElement>) => {
@ -72,7 +67,7 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
setError(undefined); handleEstimatePointError && handleEstimatePointError(estimateInputValue, undefined, "delete");
if (estimateInputValue) { if (estimateInputValue) {
const currentEstimateType: EEstimateSystem | undefined = estimateType; const currentEstimateType: EEstimateSystem | undefined = estimateType;
@ -91,7 +86,7 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
isEstimateValid = true; isEstimateValid = true;
} }
} else if (currentEstimateType && currentEstimateType === EEstimateSystem.CATEGORIES) { } else if (currentEstimateType && currentEstimateType === EEstimateSystem.CATEGORIES) {
if (estimateInputValue && estimateInputValue.length > 0) { if (estimateInputValue && estimateInputValue.length > 0 && Number(estimateInputValue) < 0) {
isEstimateValid = true; isEstimateValid = true;
} }
} }
@ -108,7 +103,7 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
await creteEstimatePoint(workspaceSlug, projectId, payload); await creteEstimatePoint(workspaceSlug, projectId, payload);
setLoader(false); setLoader(false);
setError(undefined); handleEstimatePointError && handleEstimatePointError(estimateInputValue, undefined, "delete");
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Estimate point created", title: "Estimate point created",
@ -117,7 +112,11 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
handleClose(); handleClose();
} catch { } catch {
setLoader(false); setLoader(false);
setError("We are unable to process your request, please try again."); handleEstimatePointError &&
handleEstimatePointError(
estimateInputValue,
"We are unable to process your request, please try again."
);
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Estimate point creation failed", title: "Estimate point creation failed",
@ -132,22 +131,24 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
} }
} else { } else {
setLoader(false); setLoader(false);
setError( handleEstimatePointError &&
[EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateType) handleEstimatePointError(
? "Estimate point needs to be a numeric value." estimateInputValue,
: "Estimate point needs to be a character value." [EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateType)
); ? "Estimate point needs to be a numeric value."
: "Estimate point needs to be a character value."
);
} }
} else setError("Estimate value already exists."); } else handleEstimatePointError && handleEstimatePointError(estimateInputValue, "Estimate value already exists.");
} else setError("Estimate value cannot be empty."); } else handleEstimatePointError && handleEstimatePointError(estimateInputValue, "Estimate value cannot be empty.");
}; };
return ( return (
<form onSubmit={handleCreate} className="relative flex items-center gap-2 text-base"> <form onSubmit={handleCreate} className="relative flex items-center gap-2 text-base pr-2.5">
<div <div
className={cn( className={cn(
"relative w-full border rounded flex items-center my-1", "relative w-full border rounded flex items-center my-1",
error ? `border-red-500` : `border-custom-border-200` estimatePointError?.message ? `border-red-500` : `border-custom-border-200`
)} )}
> >
<input <input
@ -158,8 +159,8 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
placeholder="Enter estimate point" placeholder="Enter estimate point"
autoFocus autoFocus
/> />
{error && ( {estimatePointError?.message && (
<Tooltip tooltipContent={error} position="bottom"> <Tooltip tooltipContent={estimatePointError?.message} position="bottom">
<div className="flex-shrink-0 w-3.5 h-3.5 overflow-hidden mr-3 relative flex justify-center items-center text-red-500"> <div className="flex-shrink-0 w-3.5 h-3.5 overflow-hidden mr-3 relative flex justify-center items-center text-red-500">
<Info size={14} /> <Info size={14} />
</div> </div>

View File

@ -1,7 +1,7 @@
import { FC, useEffect, useRef, useState } from "react"; import { FC, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { GripVertical, Pencil, Trash2 } from "lucide-react"; import { GripVertical, Pencil, Trash2 } from "lucide-react";
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types"; import { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeErrorObject } from "@plane/types";
// components // components
import { EstimatePointUpdate, EstimatePointDelete } from "@/components/estimates/points"; import { EstimatePointUpdate, EstimatePointDelete } from "@/components/estimates/points";
// plane web constants // plane web constants
@ -17,6 +17,8 @@ type TEstimatePointItemPreview = {
estimatePoints: TEstimatePointsObject[]; estimatePoints: TEstimatePointsObject[];
handleEstimatePointValueUpdate?: (estimateValue: string) => void; handleEstimatePointValueUpdate?: (estimateValue: string) => void;
handleEstimatePointValueRemove?: () => void; handleEstimatePointValueRemove?: () => void;
estimatePointError?: TEstimateTypeErrorObject | undefined;
handleEstimatePointError?: (newValue: string, message: string | undefined) => void;
}; };
export const EstimatePointItemPreview: FC<TEstimatePointItemPreview> = observer((props) => { export const EstimatePointItemPreview: FC<TEstimatePointItemPreview> = observer((props) => {
@ -30,6 +32,8 @@ export const EstimatePointItemPreview: FC<TEstimatePointItemPreview> = observer(
estimatePoints, estimatePoints,
handleEstimatePointValueUpdate, handleEstimatePointValueUpdate,
handleEstimatePointValueRemove, handleEstimatePointValueRemove,
estimatePointError,
handleEstimatePointError,
} = props; } = props;
// state // state
const [estimatePointEditToggle, setEstimatePointEditToggle] = useState(false); const [estimatePointEditToggle, setEstimatePointEditToggle] = useState(false);
@ -90,6 +94,8 @@ export const EstimatePointItemPreview: FC<TEstimatePointItemPreview> = observer(
handleEstimatePointValueUpdate && handleEstimatePointValueUpdate(estimatePointValue) handleEstimatePointValueUpdate && handleEstimatePointValueUpdate(estimatePointValue)
} }
closeCallBack={() => setEstimatePointEditToggle(false)} closeCallBack={() => setEstimatePointEditToggle(false)}
estimatePointError={estimatePointError}
handleEstimatePointError={handleEstimatePointError}
/> />
)} )}

View File

@ -3,7 +3,7 @@
import { FC, useEffect, useState, FormEvent } from "react"; import { FC, useEffect, useState, FormEvent } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Check, Info, X } from "lucide-react"; import { Check, Info, X } from "lucide-react";
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types"; import { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeErrorObject } from "@plane/types";
import { Spinner, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; import { Spinner, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
@ -23,6 +23,8 @@ type TEstimatePointUpdate = {
estimatePoint: TEstimatePointsObject; estimatePoint: TEstimatePointsObject;
handleEstimatePointValueUpdate: (estimateValue: string) => void; handleEstimatePointValueUpdate: (estimateValue: string) => void;
closeCallBack: () => void; closeCallBack: () => void;
estimatePointError?: TEstimateTypeErrorObject | undefined;
handleEstimatePointError?: (newValue: string, message: string | undefined, mode?: "add" | "delete") => void;
}; };
export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) => { export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) => {
@ -36,13 +38,14 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
estimatePoint, estimatePoint,
handleEstimatePointValueUpdate, handleEstimatePointValueUpdate,
closeCallBack, closeCallBack,
estimatePointError,
handleEstimatePointError,
} = props; } = props;
// hooks // hooks
const { updateEstimatePoint } = useEstimatePoint(estimateId, estimatePointId); const { updateEstimatePoint } = useEstimatePoint(estimateId, estimatePointId);
// states // states
const [loader, setLoader] = useState(false); const [loader, setLoader] = useState(false);
const [estimateInputValue, setEstimateInputValue] = useState<string | undefined>(undefined); const [estimateInputValue, setEstimateInputValue] = useState<string | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
useEffect(() => { useEffect(() => {
if (estimateInputValue === undefined && estimatePoint) setEstimateInputValue(estimatePoint?.value || ""); if (estimateInputValue === undefined && estimatePoint) setEstimateInputValue(estimatePoint?.value || "");
@ -60,7 +63,7 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
}; };
const handleEstimateInputValue = (value: string) => { const handleEstimateInputValue = (value: string) => {
setError(undefined); handleEstimatePointError && handleEstimatePointError(value, undefined);
setEstimateInputValue(() => value); setEstimateInputValue(() => value);
}; };
@ -69,7 +72,7 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
setError(undefined); handleEstimatePointError && handleEstimatePointError(estimateInputValue || "", undefined, "delete");
if (estimateInputValue) { if (estimateInputValue) {
const currentEstimateType: EEstimateSystem | undefined = estimateType; const currentEstimateType: EEstimateSystem | undefined = estimateType;
@ -97,7 +100,8 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
if (estimateId != undefined) { if (estimateId != undefined) {
if (estimateInputValue === estimatePoint.value) { if (estimateInputValue === estimatePoint.value) {
setLoader(false); setLoader(false);
setError(undefined); handleEstimatePointError && handleEstimatePointError(estimateInputValue, undefined);
handleClose(); handleClose();
} else } else
try { try {
@ -109,7 +113,7 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
await updateEstimatePoint(workspaceSlug, projectId, payload); await updateEstimatePoint(workspaceSlug, projectId, payload);
setLoader(false); setLoader(false);
setError(undefined); handleEstimatePointError && handleEstimatePointError(estimateInputValue, undefined, "delete");
handleClose(); handleClose();
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
@ -118,7 +122,11 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
}); });
} catch { } catch {
setLoader(false); setLoader(false);
setError("We are unable to process your request, please try again."); handleEstimatePointError &&
handleEstimatePointError(
estimateInputValue,
"We are unable to process your request, please try again."
);
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Estimate modification failed", title: "Estimate modification failed",
@ -130,22 +138,25 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
} }
} else { } else {
setLoader(false); setLoader(false);
setError( handleEstimatePointError &&
[EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateType) handleEstimatePointError(
? "Estimate point needs to be a numeric value." estimateInputValue,
: "Estimate point needs to be a character value." [EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateType)
); ? "Estimate point needs to be a numeric value."
: "Estimate point needs to be a character value."
);
} }
} else setError("Estimate value already exists."); } else handleEstimatePointError && handleEstimatePointError(estimateInputValue, "Estimate value already exists.");
} else setError("Estimate value cannot be empty."); } else
handleEstimatePointError && handleEstimatePointError(estimateInputValue || "", "Estimate value cannot be empty.");
}; };
return ( return (
<form onSubmit={handleUpdate} className="relative flex items-center gap-2 text-base"> <form onSubmit={handleUpdate} className="relative flex items-center gap-2 text-base pr-2.5">
<div <div
className={cn( className={cn(
"relative w-full border rounded flex items-center my-1", "relative w-full border rounded flex items-center my-1",
error ? `border-red-500` : `border-custom-border-200` estimatePointError?.message ? `border-red-500` : `border-custom-border-200`
)} )}
> >
<input <input
@ -156,9 +167,9 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
placeholder="Enter estimate point" placeholder="Enter estimate point"
autoFocus autoFocus
/> />
{error && ( {estimatePointError?.message && (
<> <>
<Tooltip tooltipContent={error} position="bottom"> <Tooltip tooltipContent={estimatePointError?.message} position="bottom">
<div className="flex-shrink-0 w-3.5 h-3.5 overflow-hidden mr-3 relative flex justify-center items-center text-red-500"> <div className="flex-shrink-0 w-3.5 h-3.5 overflow-hidden mr-3 relative flex justify-center items-center text-red-500">
<Info size={14} /> <Info size={14} />
</div> </div>