mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: editing and deleting the existing estimate updates
This commit is contained in:
parent
1e59d5b735
commit
bdb0674d35
@ -5,7 +5,7 @@ import { Button, Sortable } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { EstimatePointItem } from "@/components/estimates";
|
import { EstimatePointItem } from "@/components/estimates";
|
||||||
// constants
|
// constants
|
||||||
import { EEstimateSystem, EEstimateUpdateStages, ESTIMATE_SYSTEMS } from "@/constants/estimates";
|
import { EEstimateSystem, EEstimateUpdateStages, ESTIMATE_SYSTEMS, maxEstimatesCount } from "@/constants/estimates";
|
||||||
|
|
||||||
type TEstimateCreateStageTwo = {
|
type TEstimateCreateStageTwo = {
|
||||||
estimateSystem: EEstimateSystem;
|
estimateSystem: EEstimateSystem;
|
||||||
@ -17,7 +17,6 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
|
|||||||
const { estimateSystem, estimatePoints, handleEstimatePoints } = props;
|
const { estimateSystem, estimatePoints, handleEstimatePoints } = props;
|
||||||
|
|
||||||
const currentEstimateSystem = ESTIMATE_SYSTEMS[estimateSystem] || undefined;
|
const currentEstimateSystem = ESTIMATE_SYSTEMS[estimateSystem] || undefined;
|
||||||
const maxEstimatesCount = 11;
|
|
||||||
|
|
||||||
const addNewEstimationPoint = () => {
|
const addNewEstimationPoint = () => {
|
||||||
const currentEstimationPoints = estimatePoints;
|
const currentEstimationPoints = estimatePoints;
|
||||||
@ -60,6 +59,7 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
|
|||||||
data={estimatePoints}
|
data={estimatePoints}
|
||||||
render={(value: TEstimatePointsObject, index: number) => (
|
render={(value: TEstimatePointsObject, index: number) => (
|
||||||
<EstimatePointItem
|
<EstimatePointItem
|
||||||
|
estimateId={undefined}
|
||||||
mode={EEstimateUpdateStages.CREATE}
|
mode={EEstimateUpdateStages.CREATE}
|
||||||
item={value}
|
item={value}
|
||||||
editItem={(value: string) => editEstimationPoint(index, value)}
|
editItem={(value: string) => editEstimationPoint(index, value)}
|
||||||
|
@ -1,67 +1,83 @@
|
|||||||
import { FC, Fragment, useEffect, useRef, useState } from "react";
|
import { FC, Fragment, useEffect, useRef, useState } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
import { Check, GripVertical, MoveRight, Pencil, Trash2, X } from "lucide-react";
|
import { Check, GripVertical, MoveRight, Pencil, Trash2, X } from "lucide-react";
|
||||||
import { Select } from "@headlessui/react";
|
import { Select } from "@headlessui/react";
|
||||||
import { TEstimatePointsObject } from "@plane/types";
|
import { TEstimatePointsObject } from "@plane/types";
|
||||||
import { Draggable } from "@plane/ui";
|
import { Draggable, Spinner } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { EEstimateUpdateStages } from "@/constants/estimates";
|
import { EEstimateUpdateStages } from "@/constants/estimates";
|
||||||
// components
|
// helpers
|
||||||
import { InlineEdit } from "./inline-editable";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
import { useEstimate } from "@/hooks/store";
|
||||||
|
|
||||||
type TEstimatePointItem = {
|
type TEstimatePointItem = {
|
||||||
|
estimateId: string | undefined;
|
||||||
mode: EEstimateUpdateStages;
|
mode: EEstimateUpdateStages;
|
||||||
item: TEstimatePointsObject;
|
item: TEstimatePointsObject;
|
||||||
editItem: (value: string) => void;
|
editItem: (value: string) => void;
|
||||||
deleteItem: () => void;
|
deleteItem: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EstimatePointItem: FC<TEstimatePointItem> = (props) => {
|
export const EstimatePointItem: FC<TEstimatePointItem> = observer((props) => {
|
||||||
// props
|
// props
|
||||||
const { mode, item, editItem, deleteItem } = props;
|
const { estimateId, mode, item, editItem, deleteItem } = props;
|
||||||
const { id, key, value } = item;
|
const { id, key, value } = item;
|
||||||
|
// hooks
|
||||||
|
const { asJson: estimate, updateEstimate, deleteEstimate } = useEstimate(estimateId);
|
||||||
// ref
|
// ref
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
// states
|
// states
|
||||||
const [inputValue, setInputValue] = useState<string | undefined>(undefined);
|
const [inputValue, setInputValue] = useState<string | undefined>(undefined);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
// handling editing states
|
||||||
const [showDeleteUI, setShowDeleteUI] = useState(false);
|
const [estimateEditLoader, setEstimateEditLoader] = useState(false);
|
||||||
|
const [deletedEstimateValue, setDeletedEstimateValue] = useState<string | undefined>(undefined);
|
||||||
|
const [isEstimateEditing, setIsEstimateEditing] = useState(false);
|
||||||
|
const [isEstimateDeleting, setIsEstimateDeleting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputValue === undefined) setInputValue(value);
|
if (inputValue === undefined || inputValue != value) setInputValue(value);
|
||||||
}, [value, inputValue]);
|
}, [value, inputValue]);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleCreateEdit = (value: string) => {
|
||||||
if (id) {
|
setInputValue(value);
|
||||||
// Make the api call to save the estimate point
|
editItem(value);
|
||||||
// Show a spinner
|
|
||||||
setIsEditing(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (value: string) => {
|
const handleEdit = async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
setIsEditing(true);
|
try {
|
||||||
setTimeout(() => {
|
setEstimateEditLoader(true);
|
||||||
inputRef.current?.focus();
|
await updateEstimate({ estimate_points: [{ id: id, key: key, value: value }] });
|
||||||
inputRef.current?.select();
|
setIsEstimateEditing(false);
|
||||||
});
|
setEstimateEditLoader(false);
|
||||||
|
} catch (error) {
|
||||||
|
setEstimateEditLoader(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setInputValue(value);
|
if (inputValue) editItem(inputValue);
|
||||||
editItem(value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
setShowDeleteUI(true);
|
try {
|
||||||
|
setEstimateEditLoader(true);
|
||||||
|
await deleteEstimate(deletedEstimateValue);
|
||||||
|
setIsEstimateDeleting(false);
|
||||||
|
setEstimateEditLoader(false);
|
||||||
|
} catch (error) {
|
||||||
|
setEstimateEditLoader(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
deleteItem();
|
deleteItem();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectDropdownOptions = estimate && estimate?.points ? estimate?.points.filter((point) => point.id !== id) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable data={item}>
|
<Draggable data={item}>
|
||||||
{mode === EEstimateUpdateStages.CREATE && (
|
{!id && (
|
||||||
<div className="border border-custom-border-200 rounded relative flex items-center px-2.5 gap-2">
|
<div className="border border-custom-border-200 rounded relative flex items-center px-2.5 gap-2">
|
||||||
<div 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 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">
|
||||||
<GripVertical size={14} className="text-custom-text-200" />
|
<GripVertical size={14} className="text-custom-text-200" />
|
||||||
@ -70,8 +86,8 @@ const EstimatePointItem: FC<TEstimatePointItem> = (props) => {
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => handleEdit(e.target.value)}
|
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"
|
className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5 w-full"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
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"
|
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"
|
||||||
@ -82,122 +98,120 @@ const EstimatePointItem: FC<TEstimatePointItem> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{mode === EEstimateUpdateStages.EDIT && (
|
{id && (
|
||||||
<>
|
<>
|
||||||
<div className="border border-custom-border-200 rounded relative flex items-center px-2.5 gap-2">
|
{mode === EEstimateUpdateStages.EDIT && (
|
||||||
<div 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">
|
<>
|
||||||
<GripVertical size={14} className="text-custom-text-200" />
|
{!isEstimateEditing && !isEstimateDeleting && (
|
||||||
|
<div className="border border-custom-border-200 rounded relative flex items-center px-2.5 gap-2">
|
||||||
|
<div 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">
|
||||||
|
<GripVertical size={14} className="text-custom-text-200" />
|
||||||
|
</div>
|
||||||
|
<div className="py-2.5 flex-grow" onClick={() => setIsEstimateEditing(true)}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
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={() => setIsEstimateEditing(true)}
|
||||||
|
>
|
||||||
|
<Pencil size={14} className="text-custom-text-200" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
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={() => setIsEstimateDeleting(true)}
|
||||||
|
>
|
||||||
|
<Trash2 size={14} className="text-custom-text-200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(isEstimateEditing || isEstimateDeleting) && (
|
||||||
|
<div className="relative flex items-center gap-2">
|
||||||
|
<div className="flex-grow relative flex items-center gap-3">
|
||||||
|
<div className="flex-grow border border-custom-border-200 rounded">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => 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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isEstimateDeleting && <MoveRight size={14} />}
|
||||||
|
{isEstimateDeleting && (
|
||||||
|
<div className="relative flex-grow rounded border border-custom-border-200 flex items-center gap-3 p-2.5">
|
||||||
|
<Select
|
||||||
|
className="bg-transparent flex-grow focus:ring-0 focus:border-0 focus:outline-none"
|
||||||
|
value={deletedEstimateValue}
|
||||||
|
onChange={(e) => setDeletedEstimateValue(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value={undefined}>None</option>
|
||||||
|
{selectDropdownOptions.map((option) => (
|
||||||
|
<option key={option?.id} value={option?.value}>
|
||||||
|
{option?.value}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{estimateEditLoader ? (
|
||||||
|
<div className="w-6 h-6 flex-shrink-0 relative flex justify-center items-center rota">
|
||||||
|
<Spinner className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer",
|
||||||
|
isEstimateEditing ? `text-green-500` : `text-red-500`
|
||||||
|
)}
|
||||||
|
onClick={() => (isEstimateEditing ? handleEdit() : handleDelete())}
|
||||||
|
>
|
||||||
|
{isEstimateEditing ? <Check size={14} /> : <Trash2 size={14} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
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={() => (isEstimateEditing ? setIsEstimateEditing(false) : setIsEstimateDeleting(false))}
|
||||||
|
>
|
||||||
|
<X size={14} className="text-custom-text-200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === EEstimateUpdateStages.SWITCH && (
|
||||||
|
<div className="flex-grow relative flex items-center gap-3">
|
||||||
|
<div className="flex-grow border border-custom-border-200 rounded">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => 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
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<MoveRight size={14} />
|
||||||
|
<div className="flex-grow border border-custom-border-200 rounded">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
)}
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
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={handleDelete}
|
|
||||||
>
|
|
||||||
<Pencil size={14} className="text-custom-text-200" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
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={handleDelete}
|
|
||||||
>
|
|
||||||
<Trash2 size={14} className="text-custom-text-200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{mode === EEstimateUpdateStages.SWITCH && (
|
|
||||||
<div className="border border-custom-border-200 rounded relative flex items-center px-2.5 gap-2">
|
|
||||||
<div 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">
|
|
||||||
<GripVertical size={14} className="text-custom-text-200" />
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
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={handleDelete}
|
|
||||||
>
|
|
||||||
<Pencil size={14} className="text-custom-text-200" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
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={handleDelete}
|
|
||||||
>
|
|
||||||
<Trash2 size={14} className="text-custom-text-200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* <div className="border border-custom-border-200 rounded relative flex items-center px-2.5 gap-3">
|
|
||||||
<GripVertical size={14} className="text-custom-text-200" />
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
value={value}
|
|
||||||
onChange={() => {}}
|
|
||||||
className="flex-grow border-none bg-transparent focus:ring-0 focus:border-0 focus:outline-none py-2.5"
|
|
||||||
/>
|
|
||||||
<Pencil size={14} className="text-custom-text-200" />
|
|
||||||
<Trash2 size={14} className="text-custom-text-200" />
|
|
||||||
<Check size={14} className="text-custom-text-200" />
|
|
||||||
<X size={14} className="text-custom-text-200" />
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* {isEditing && (
|
|
||||||
<div className="flex justify-between items-center gap-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={value}
|
|
||||||
onChange={() => {}}
|
|
||||||
className="border rounded-md border-custom-border-300 p-3 flex-grow"
|
|
||||||
ref={inputRef}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div className="flex gap-4 justify-between items-center">
|
|
||||||
<Check className="w-6 h-6" onClick={handleSave} />
|
|
||||||
<X className="w-6 h-6" onClick={() => setIsEditing(false)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isEditing && (
|
|
||||||
<div className="border rounded-md border-custom-border-300 mb-2 p-3 flex justify-between items-center">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<GripVertical className="w-4 h-4" />
|
|
||||||
{!showDeleteUI ? <InlineEdit value={value} /> : value}
|
|
||||||
{showDeleteUI && (
|
|
||||||
<Fragment>
|
|
||||||
<MoveRight className="w-4 h-4 mx-2" />
|
|
||||||
<Select>
|
|
||||||
<option value="active">Active</option>
|
|
||||||
<option value="paused">Paused</option>
|
|
||||||
<option value="delayed">Delayed</option>
|
|
||||||
<option value="canceled">Canceled</option>
|
|
||||||
</Select>
|
|
||||||
<Check className="w-4 h-4 rounded-md" />
|
|
||||||
<X className="w-4 h-4 rounded-md" onClick={() => setShowDeleteUI(false)} />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-4 items-center">
|
|
||||||
<Pencil className="w-4 h-4" onClick={handleEdit} />
|
|
||||||
{!showDeleteUI && <Trash2 className="w-4 h-4" onClick={handleDelete} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
</Draggable>
|
</Draggable>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export { EstimatePointItem };
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { FC, useEffect, useMemo, useState } from "react";
|
import { FC, useEffect, useMemo, useState } from "react";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ChevronLeft } from "lucide-react";
|
import { ChevronLeft } from "lucide-react";
|
||||||
import { IEstimateFormData, TEstimatePointsObject, TEstimateUpdateStageKeys, TEstimateSystemKeys } from "@plane/types";
|
import { IEstimateFormData, TEstimatePointsObject, TEstimateUpdateStageKeys, TEstimateSystemKeys } from "@plane/types";
|
||||||
@ -46,10 +45,7 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => {
|
const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints);
|
||||||
const points = cloneDeep(newPoints);
|
|
||||||
setEstimatePoints(points);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@ -109,8 +105,6 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("estimateStage", estimateEditType);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
||||||
<div className="relative space-y-6 py-5">
|
<div className="relative space-y-6 py-5">
|
||||||
|
@ -4,6 +4,8 @@ import { IEstimate, TEstimatePointsObject, TEstimateUpdateStageKeys } from "@pla
|
|||||||
import { Button, Sortable } from "@plane/ui";
|
import { Button, Sortable } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { EstimatePointItem } from "@/components/estimates";
|
import { EstimatePointItem } from "@/components/estimates";
|
||||||
|
// constants
|
||||||
|
import { EEstimateUpdateStages, maxEstimatesCount } from "@/constants/estimates";
|
||||||
|
|
||||||
type TEstimateUpdateStageTwo = {
|
type TEstimateUpdateStageTwo = {
|
||||||
estimate: IEstimate;
|
estimate: IEstimate;
|
||||||
@ -19,7 +21,6 @@ export const EstimateUpdateStageTwo: FC<TEstimateUpdateStageTwo> = (props) => {
|
|||||||
|
|
||||||
const addNewEstimationPoint = () => {
|
const addNewEstimationPoint = () => {
|
||||||
const currentEstimationPoints = estimatePoints;
|
const currentEstimationPoints = estimatePoints;
|
||||||
|
|
||||||
const newEstimationPoint: TEstimatePointsObject = {
|
const newEstimationPoint: TEstimatePointsObject = {
|
||||||
key: currentEstimationPoints.length + 1,
|
key: currentEstimationPoints.length + 1,
|
||||||
value: "0",
|
value: "0",
|
||||||
@ -27,32 +28,61 @@ export const EstimateUpdateStageTwo: FC<TEstimateUpdateStageTwo> = (props) => {
|
|||||||
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]);
|
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEstimationPoint = (index: number) => {
|
const editEstimationPoint = (index: number, value: string) => {
|
||||||
const newEstimationPoints = estimatePoints;
|
const newEstimationPoints = estimatePoints;
|
||||||
newEstimationPoints.splice(index, 1);
|
newEstimationPoints[index].value = value;
|
||||||
handleEstimatePoints(newEstimationPoints);
|
handleEstimatePoints(newEstimationPoints);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedSortedKeys = (updatedEstimatePoints: TEstimatePointsObject[]) =>
|
const deleteEstimationPoint = (index: number) => {
|
||||||
updatedEstimatePoints.map((item, index) => ({
|
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,
|
...item,
|
||||||
key: index + 1,
|
key: index + 1,
|
||||||
})) as TEstimatePointsObject[];
|
})) as TEstimatePointsObject[];
|
||||||
|
return sortedEstimatePoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!estimateEditType) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-1">
|
||||||
<Sortable
|
<div className="text-sm font-medium text-custom-text-300">
|
||||||
data={estimatePoints}
|
{estimateEditType === EEstimateUpdateStages.SWITCH ? "Estimate type switching" : currentEstimateSystem?.type}
|
||||||
render={(value: TEstimatePointsObject, index: number) => (
|
</div>
|
||||||
<EstimatePointItem item={value} deleteItem={() => deleteEstimationPoint(index)} />
|
<div className="space-y-3">
|
||||||
|
<Sortable
|
||||||
|
data={estimatePoints}
|
||||||
|
render={(value: TEstimatePointsObject, index: number) => (
|
||||||
|
<EstimatePointItem
|
||||||
|
estimateId={estimate?.id || undefined}
|
||||||
|
mode={estimateEditType}
|
||||||
|
item={value}
|
||||||
|
editItem={(value: string) => 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 && (
|
||||||
|
<Button size="sm" prependIcon={<Plus />} onClick={addNewEstimationPoint}>
|
||||||
|
Add {currentEstimateSystem?.type}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))}
|
</div>
|
||||||
keyExtractor={(item: TEstimatePointsObject) => item?.id?.toString() || item.value.toString()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button prependIcon={<Plus />} onClick={addNewEstimationPoint}>
|
|
||||||
Add {currentEstimateSystem?.name}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// types
|
||||||
|
import { TEstimateSystems } from "@plane/types";
|
||||||
|
|
||||||
export enum EEstimateSystem {
|
export enum EEstimateSystem {
|
||||||
POINTS = "points",
|
POINTS = "points",
|
||||||
CATEGORIES = "categories",
|
CATEGORIES = "categories",
|
||||||
@ -10,8 +13,7 @@ export enum EEstimateUpdateStages {
|
|||||||
SWITCH = "switch",
|
SWITCH = "switch",
|
||||||
}
|
}
|
||||||
|
|
||||||
// types
|
export const maxEstimatesCount = 11;
|
||||||
import { TEstimateSystems } from "@plane/types";
|
|
||||||
|
|
||||||
export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
export const ESTIMATE_SYSTEMS: TEstimateSystems = {
|
||||||
points: {
|
points: {
|
||||||
|
@ -28,11 +28,11 @@ export interface IEstimate extends IEstimateType {
|
|||||||
estimatePoints: Record<string, IEstimatePoint>;
|
estimatePoints: Record<string, IEstimatePoint>;
|
||||||
// computed
|
// computed
|
||||||
asJson: IEstimateType;
|
asJson: IEstimateType;
|
||||||
EstimatePointIds: string[] | undefined;
|
estimatePointIds: string[] | undefined;
|
||||||
estimatePointById: (estimateId: string) => IEstimatePointType | undefined;
|
estimatePointById: (estimateId: string) => IEstimatePointType | undefined;
|
||||||
// actions
|
// actions
|
||||||
updateEstimate: (payload: IEstimateFormData) => Promise<void>;
|
updateEstimate: (payload: IEstimateFormData) => Promise<void>;
|
||||||
deleteEstimate: (estimatePointId: string) => Promise<void>;
|
deleteEstimate: (estimatePointId: string | undefined) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Estimate implements IEstimate {
|
export class Estimate implements IEstimate {
|
||||||
@ -80,7 +80,7 @@ export class Estimate implements IEstimate {
|
|||||||
estimatePoints: observable,
|
estimatePoints: observable,
|
||||||
// computed
|
// computed
|
||||||
asJson: computed,
|
asJson: computed,
|
||||||
EstimatePointIds: computed,
|
estimatePointIds: computed,
|
||||||
// actions
|
// actions
|
||||||
updateEstimate: action,
|
updateEstimate: action,
|
||||||
deleteEstimate: action,
|
deleteEstimate: action,
|
||||||
@ -126,7 +126,7 @@ export class Estimate implements IEstimate {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get EstimatePointIds() {
|
get estimatePointIds() {
|
||||||
const { estimatePoints } = this;
|
const { estimatePoints } = this;
|
||||||
if (!estimatePoints) return undefined;
|
if (!estimatePoints) return undefined;
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ export class Estimate implements IEstimate {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteEstimate = async (estimatePointId: string) => {
|
deleteEstimate = async (estimatePointId: string | undefined) => {
|
||||||
try {
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.router;
|
const { workspaceSlug, projectId } = this.store.router;
|
||||||
if (!workspaceSlug || !projectId || !estimatePointId) return;
|
if (!workspaceSlug || !projectId || !estimatePointId) return;
|
||||||
|
Loading…
Reference in New Issue
Block a user