mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: updated modal and form validations (#4748)
This commit is contained in:
parent
666d35afb9
commit
423bc15119
@ -17,7 +17,7 @@ export enum EModalWidth {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
handleClose: () => void;
|
handleClose?: () => void;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
position?: EModalPosition;
|
position?: EModalPosition;
|
||||||
width?: EModalWidth;
|
width?: EModalWidth;
|
||||||
@ -27,7 +27,7 @@ export const ModalCore: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={Fragment}>
|
<Transition.Root show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
<Dialog as="div" className="relative z-20" onClose={() => handleClose && handleClose}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
|
@ -28,6 +28,8 @@ 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 [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);
|
||||||
@ -40,33 +42,39 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
|
|||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleCreateEstimate = async () => {
|
const handleCreateEstimate = async () => {
|
||||||
try {
|
setEstimatePointCreateError([]);
|
||||||
if (!workspaceSlug || !projectId || !estimatePoints) return;
|
if (estimatePointCreate === undefined || estimatePointCreate?.length === 0) {
|
||||||
setButtonLoader(true);
|
try {
|
||||||
const payload: IEstimateFormData = {
|
if (!workspaceSlug || !projectId || !estimatePoints) return;
|
||||||
estimate: {
|
|
||||||
name: ESTIMATE_SYSTEMS[estimateSystem]?.name,
|
|
||||||
type: estimateSystem,
|
|
||||||
last_used: true,
|
|
||||||
},
|
|
||||||
estimate_points: estimatePoints,
|
|
||||||
};
|
|
||||||
await createEstimate(workspaceSlug, projectId, payload);
|
|
||||||
|
|
||||||
setButtonLoader(false);
|
setButtonLoader(true);
|
||||||
setToast({
|
const payload: IEstimateFormData = {
|
||||||
type: TOAST_TYPE.SUCCESS,
|
estimate: {
|
||||||
title: "Estimate created",
|
name: ESTIMATE_SYSTEMS[estimateSystem]?.name,
|
||||||
message: "A new estimate has been added in your project.",
|
type: estimateSystem,
|
||||||
});
|
last_used: true,
|
||||||
handleClose();
|
},
|
||||||
} catch (error) {
|
estimate_points: estimatePoints,
|
||||||
setButtonLoader(false);
|
};
|
||||||
setToast({
|
await createEstimate(workspaceSlug, projectId, payload);
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: "Estimate creation failed",
|
setButtonLoader(false);
|
||||||
message: "We were unable to create the new estimate, please try again.",
|
setToast({
|
||||||
});
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
title: "Estimate created",
|
||||||
|
message: "A new estimate has been added in 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.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setEstimatePointCreateError(estimatePointCreate.map((point) => point.key));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,7 +82,7 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
|
|||||||
const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]);
|
const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
||||||
<div className="relative space-y-6 py-5">
|
<div className="relative space-y-6 py-5">
|
||||||
{/* heading */}
|
{/* heading */}
|
||||||
<div className="relative flex justify-between items-center gap-2 px-5">
|
<div className="relative flex justify-between items-center gap-2 px-5">
|
||||||
@ -90,7 +98,7 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
|
|||||||
<ChevronLeft className="w-4 h-4" />
|
<ChevronLeft className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-xl font-medium text-custom-text-100">New Estimate System</div>
|
<div className="text-xl font-medium text-custom-text-100">New estimate system</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">Step {renderEstimateStepsCount} of 2</div>
|
<div className="text-xs text-gray-400">Step {renderEstimateStepsCount} of 2</div>
|
||||||
</div>
|
</div>
|
||||||
@ -107,16 +115,26 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{estimatePoints && (
|
{estimatePoints && (
|
||||||
<>
|
<EstimatePointCreateRoot
|
||||||
<EstimatePointCreateRoot
|
workspaceSlug={workspaceSlug}
|
||||||
workspaceSlug={workspaceSlug}
|
projectId={projectId}
|
||||||
projectId={projectId}
|
estimateId={undefined}
|
||||||
estimateId={undefined}
|
estimateType={estimateSystem}
|
||||||
estimateType={estimateSystem}
|
estimatePoints={estimatePoints}
|
||||||
estimatePoints={estimatePoints}
|
setEstimatePoints={setEstimatePoints}
|
||||||
setEstimatePoints={setEstimatePoints}
|
estimatePointCreate={estimatePointCreate}
|
||||||
/>
|
setEstimatePointCreate={(value) => {
|
||||||
</>
|
setEstimatePointCreateError([]);
|
||||||
|
setEstimatePointCreate(value);
|
||||||
|
}}
|
||||||
|
estimatePointCreateError={estimatePointCreateError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{estimatePointCreateError.length > 0 && (
|
||||||
|
<div className="pt-5 text-sm text-red-500">
|
||||||
|
Estimate points can't be empty. Enter a value in each field or remove those you don't have
|
||||||
|
values for.
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ export const DeleteEstimateModal: FC<TDeleteEstimateModal> = observer((props) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
||||||
<div className="relative space-y-6 py-5">
|
<div className="relative space-y-6 py-5">
|
||||||
{/* heading */}
|
{/* heading */}
|
||||||
<div className="relative flex justify-between items-center gap-2 px-5">
|
<div className="relative flex justify-between items-center gap-2 px-5">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Dispatch, FC, SetStateAction, useCallback, useState } from "react";
|
import { Dispatch, FC, SetStateAction, useCallback } 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 } from "@plane/types";
|
||||||
@ -17,13 +17,24 @@ type TEstimatePointCreateRoot = {
|
|||||||
estimateType: TEstimateSystemKeys;
|
estimateType: TEstimateSystemKeys;
|
||||||
estimatePoints: TEstimatePointsObject[];
|
estimatePoints: TEstimatePointsObject[];
|
||||||
setEstimatePoints: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>;
|
setEstimatePoints: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>;
|
||||||
|
estimatePointCreate: TEstimatePointsObject[] | undefined;
|
||||||
|
setEstimatePointCreate: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>;
|
||||||
|
estimatePointCreateError: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((props) => {
|
export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((props) => {
|
||||||
// props
|
// props
|
||||||
const { workspaceSlug, projectId, estimateId, estimateType, estimatePoints, setEstimatePoints } = props;
|
const {
|
||||||
// states
|
workspaceSlug,
|
||||||
const [estimatePointCreate, setEstimatePointCreate] = useState<TEstimatePointsObject[] | undefined>(undefined);
|
projectId,
|
||||||
|
estimateId,
|
||||||
|
estimateType,
|
||||||
|
estimatePoints,
|
||||||
|
setEstimatePoints,
|
||||||
|
estimatePointCreate,
|
||||||
|
setEstimatePointCreate,
|
||||||
|
estimatePointCreateError,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const handleEstimatePoint = useCallback(
|
const handleEstimatePoint = useCallback(
|
||||||
(mode: "add" | "remove" | "update", value: TEstimatePointsObject) => {
|
(mode: "add" | "remove" | "update", value: TEstimatePointsObject) => {
|
||||||
@ -77,9 +88,19 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
|
|||||||
setEstimatePoints(() => updatedEstimateKeysOrder);
|
setEstimatePoints(() => updatedEstimateKeysOrder);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
if (estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= maxEstimatesCount - 1) {
|
||||||
|
handleEstimatePointCreate("add", {
|
||||||
|
id: undefined,
|
||||||
|
key: estimatePoints.length + (estimatePointCreate?.length || 0) + 1,
|
||||||
|
value: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId) return <></>;
|
if (!workspaceSlug || !projectId) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-1">
|
||||||
<div className="text-sm font-medium text-custom-text-200 capitalize">{estimateType}</div>
|
<div className="text-sm font-medium text-custom-text-200 capitalize">{estimateType}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -118,21 +139,12 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
|
|||||||
handleEstimatePoint("add", { ...estimatePoint, value: estimatePointValue })
|
handleEstimatePoint("add", { ...estimatePoint, value: estimatePointValue })
|
||||||
}
|
}
|
||||||
closeCallBack={() => handleEstimatePointCreate("remove", estimatePoint)}
|
closeCallBack={() => handleEstimatePointCreate("remove", estimatePoint)}
|
||||||
|
handleCreateCallback={() => estimatePointCreate.length === 1 && handleCreate()}
|
||||||
|
isError={estimatePointCreateError.includes(estimatePoint.key) ? true : false}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= maxEstimatesCount && (
|
{estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= maxEstimatesCount - 1 && (
|
||||||
<Button
|
<Button variant="link-primary" size="sm" prependIcon={<Plus />} onClick={handleCreate}>
|
||||||
variant="link-primary"
|
|
||||||
size="sm"
|
|
||||||
prependIcon={<Plus />}
|
|
||||||
onClick={() =>
|
|
||||||
handleEstimatePointCreate("add", {
|
|
||||||
id: undefined,
|
|
||||||
key: estimatePoints.length + (estimatePointCreate?.length || 0) + 1,
|
|
||||||
value: "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Add {estimateType}
|
Add {estimateType}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, MouseEvent, FocusEvent, useState } from "react";
|
import { FC, useState, FormEvent, useEffect } 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 } from "@plane/types";
|
||||||
@ -21,6 +21,8 @@ type TEstimatePointCreate = {
|
|||||||
estimatePoints: TEstimatePointsObject[];
|
estimatePoints: TEstimatePointsObject[];
|
||||||
handleEstimatePointValue?: (estimateValue: string) => void;
|
handleEstimatePointValue?: (estimateValue: string) => void;
|
||||||
closeCallBack: () => void;
|
closeCallBack: () => void;
|
||||||
|
handleCreateCallback: () => void;
|
||||||
|
isError: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) => {
|
export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) => {
|
||||||
@ -32,6 +34,8 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
|||||||
estimatePoints,
|
estimatePoints,
|
||||||
handleEstimatePointValue,
|
handleEstimatePointValue,
|
||||||
closeCallBack,
|
closeCallBack,
|
||||||
|
handleCreateCallback,
|
||||||
|
isError,
|
||||||
} = props;
|
} = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { creteEstimatePoint } = useEstimate(estimateId);
|
const { creteEstimatePoint } = useEstimate(estimateId);
|
||||||
@ -40,6 +44,13 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
|||||||
const [loader, setLoader] = useState(false);
|
const [loader, setLoader] = useState(false);
|
||||||
const [error, setError] = useState<string | undefined>(undefined);
|
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);
|
||||||
setEstimateInputValue("");
|
setEstimateInputValue("");
|
||||||
@ -51,7 +62,12 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
|||||||
closeCallBack();
|
closeCallBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreate = async (event: MouseEvent<HTMLButtonElement> | FocusEvent<HTMLInputElement, Element>) => {
|
const handleEstimateInputValue = (value: string) => {
|
||||||
|
setError(undefined);
|
||||||
|
setEstimateInputValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = async (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
@ -110,6 +126,9 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleSuccess(estimateInputValue);
|
handleSuccess(estimateInputValue);
|
||||||
|
if (handleCreateCallback) {
|
||||||
|
handleCreateCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setLoader(false);
|
setLoader(false);
|
||||||
@ -124,7 +143,7 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="relative flex items-center gap-2 text-base">
|
<form onSubmit={handleCreate} className="relative flex items-center gap-2 text-base">
|
||||||
<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",
|
||||||
@ -134,42 +153,37 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={estimateInputValue}
|
value={estimateInputValue}
|
||||||
onChange={(e) => setEstimateInputValue(e.target.value)}
|
onChange={(e) => handleEstimateInputValue(e.target.value)}
|
||||||
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
||||||
placeholder="Enter estimate point"
|
placeholder="Enter estimate point"
|
||||||
autoFocus
|
autoFocus
|
||||||
onBlur={(e) => !estimateId && handleCreate(e)}
|
|
||||||
/>
|
/>
|
||||||
{error && (
|
{error && (
|
||||||
<>
|
<Tooltip tooltipContent={error} position="bottom">
|
||||||
<Tooltip tooltipContent={error} 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>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{estimateId && (
|
{estimateInputValue && estimateInputValue.length > 0 && (
|
||||||
<>
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer text-green-500"
|
||||||
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer text-green-500"
|
disabled={loader}
|
||||||
disabled={loader}
|
>
|
||||||
onClick={handleCreate}
|
{loader ? <Spinner className="w-4 h-4" /> : <Check size={14} />}
|
||||||
>
|
</button>
|
||||||
{loader ? <Spinner className="w-4 h-4" /> : <Check size={14} />}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
|
|
||||||
onClick={handleClose}
|
|
||||||
disabled={loader}
|
|
||||||
>
|
|
||||||
<X size={14} className="text-custom-text-200" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
|
||||||
|
onClick={handleClose}
|
||||||
|
disabled={loader}
|
||||||
|
>
|
||||||
|
<X size={14} className="text-custom-text-200" />
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { GripVertical, Pencil, Trash2 } from "lucide-react";
|
|||||||
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types";
|
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { EstimatePointUpdate, EstimatePointDelete } from "@/components/estimates/points";
|
import { EstimatePointUpdate, EstimatePointDelete } from "@/components/estimates/points";
|
||||||
|
import { minEstimatesCount } from "@/constants/estimates";
|
||||||
|
|
||||||
type TEstimatePointItemPreview = {
|
type TEstimatePointItemPreview = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -60,16 +61,18 @@ export const EstimatePointItemPreview: FC<TEstimatePointItemPreview> = observer(
|
|||||||
>
|
>
|
||||||
<Pencil size={14} className="text-custom-text-200" />
|
<Pencil size={14} className="text-custom-text-200" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
{estimatePoints.length > minEstimatesCount && (
|
||||||
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
|
<div
|
||||||
onClick={() =>
|
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
|
||||||
estimateId && estimatePointId
|
onClick={() =>
|
||||||
? setEstimatePointDeleteToggle(true)
|
estimateId && estimatePointId
|
||||||
: handleEstimatePointValueRemove && handleEstimatePointValueRemove()
|
? setEstimatePointDeleteToggle(true)
|
||||||
}
|
: handleEstimatePointValueRemove && handleEstimatePointValueRemove()
|
||||||
>
|
}
|
||||||
<Trash2 size={14} className="text-custom-text-200" />
|
>
|
||||||
</div>
|
<Trash2 size={14} className="text-custom-text-200" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, MouseEvent, useEffect, FocusEvent, useState } 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 } from "@plane/types";
|
||||||
@ -59,7 +59,12 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
|||||||
closeCallBack();
|
closeCallBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = async (event: MouseEvent<HTMLButtonElement> | FocusEvent<HTMLInputElement, Element>) => {
|
const handleEstimateInputValue = (value: string) => {
|
||||||
|
setError(undefined);
|
||||||
|
setEstimateInputValue(() => value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
@ -71,7 +76,7 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
|||||||
let isEstimateValid = false;
|
let isEstimateValid = false;
|
||||||
|
|
||||||
const currentEstimatePointValues = estimatePoints
|
const currentEstimatePointValues = estimatePoints
|
||||||
.map((point) => (point?.id != estimatePoint?.id ? point?.value : undefined))
|
.map((point) => (point?.key != estimatePoint?.key ? point?.value : undefined))
|
||||||
.filter((value) => value != undefined) as string[];
|
.filter((value) => value != undefined) as string[];
|
||||||
const isRepeated =
|
const isRepeated =
|
||||||
(estimateType && isEstimatePointValuesRepeated(currentEstimatePointValues, estimateType, estimateInputValue)) ||
|
(estimateType && isEstimatePointValuesRepeated(currentEstimatePointValues, estimateType, estimateInputValue)) ||
|
||||||
@ -136,7 +141,7 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="relative flex items-center gap-2 text-base">
|
<form onSubmit={handleUpdate} className="relative flex items-center gap-2 text-base">
|
||||||
<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",
|
||||||
@ -146,10 +151,9 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={estimateInputValue}
|
value={estimateInputValue}
|
||||||
onChange={(e) => setEstimateInputValue(e.target.value)}
|
onChange={(e) => handleEstimateInputValue(e.target.value)}
|
||||||
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
||||||
placeholder="Enter estimate point"
|
placeholder="Enter estimate point"
|
||||||
onBlur={(e) => !estimateId && handleUpdate(e)}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{error && (
|
{error && (
|
||||||
@ -162,25 +166,24 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{estimateId && (
|
|
||||||
<>
|
{estimateInputValue && estimateInputValue.length > 0 && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer text-green-500"
|
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer text-green-500"
|
||||||
disabled={loader}
|
disabled={loader}
|
||||||
onClick={handleUpdate}
|
>
|
||||||
>
|
{loader ? <Spinner className="w-4 h-4" /> : <Check size={14} />}
|
||||||
{loader ? <Spinner className="w-4 h-4" /> : <Check size={14} />}
|
</button>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
|
|
||||||
onClick={handleClose}
|
|
||||||
disabled={loader}
|
|
||||||
>
|
|
||||||
<X size={14} className="text-custom-text-200" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
|
||||||
|
onClick={handleClose}
|
||||||
|
disabled={loader}
|
||||||
|
>
|
||||||
|
<X size={14} className="text-custom-text-200" />
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,7 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
|||||||
const { isOpen, handleClose } = props;
|
const { isOpen, handleClose } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
||||||
<div className="relative space-y-6 py-5">
|
<div className="relative space-y-6 py-5">
|
||||||
{/* heading */}
|
{/* heading */}
|
||||||
<div className="relative flex justify-between items-center gap-2 px-5">
|
<div className="relative flex justify-between items-center gap-2 px-5">
|
||||||
|
@ -13,7 +13,8 @@ export enum EEstimateUpdateStages {
|
|||||||
SWITCH = "switch",
|
SWITCH = "switch",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const maxEstimatesCount = 11;
|
export const minEstimatesCount = 2;
|
||||||
|
export const maxEstimatesCount = 6;
|
||||||
|
|
||||||
export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
||||||
points: {
|
points: {
|
||||||
@ -28,7 +29,6 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
|||||||
{ id: undefined, key: 4, value: "5" },
|
{ id: undefined, key: 4, value: "5" },
|
||||||
{ id: undefined, key: 5, value: "8" },
|
{ id: undefined, key: 5, value: "8" },
|
||||||
{ id: undefined, key: 6, value: "13" },
|
{ id: undefined, key: 6, value: "13" },
|
||||||
{ id: undefined, key: 7, value: "21" },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
linear: {
|
linear: {
|
||||||
@ -40,10 +40,6 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
|||||||
{ id: undefined, key: 4, value: "4" },
|
{ id: undefined, key: 4, value: "4" },
|
||||||
{ id: undefined, key: 5, value: "5" },
|
{ id: undefined, key: 5, value: "5" },
|
||||||
{ id: undefined, key: 6, value: "6" },
|
{ id: undefined, key: 6, value: "6" },
|
||||||
{ id: undefined, key: 7, value: "7" },
|
|
||||||
{ id: undefined, key: 8, value: "8" },
|
|
||||||
{ id: undefined, key: 9, value: "9" },
|
|
||||||
{ id: undefined, key: 10, value: "10" },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
squares: {
|
squares: {
|
||||||
@ -116,10 +112,6 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
|||||||
{ id: undefined, key: 4, value: "4" },
|
{ id: undefined, key: 4, value: "4" },
|
||||||
{ id: undefined, key: 5, value: "5" },
|
{ id: undefined, key: 5, value: "5" },
|
||||||
{ id: undefined, key: 6, value: "6" },
|
{ id: undefined, key: 6, value: "6" },
|
||||||
{ id: undefined, key: 7, value: "7" },
|
|
||||||
{ id: undefined, key: 8, value: "8" },
|
|
||||||
{ id: undefined, key: 9, value: "9" },
|
|
||||||
{ id: undefined, key: 10, value: "10" },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user