mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: integrated new endpoints
This commit is contained in:
parent
be03d90c86
commit
1cd31f4705
8
packages/types/src/estimate.d.ts
vendored
8
packages/types/src/estimate.d.ts
vendored
@ -1,4 +1,3 @@
|
||||
import { IWorkspace, IProject } from "./";
|
||||
import { EEstimateSystem, EEstimateUpdateStages } from "./enums";
|
||||
|
||||
export interface IEstimatePoint {
|
||||
@ -27,9 +26,8 @@ export interface IEstimate {
|
||||
type: TEstimateSystemKeys | undefined; // categories, points, time
|
||||
points: IEstimatePoint[] | undefined;
|
||||
workspace: string | undefined;
|
||||
workspace_detail: IWorkspace | undefined;
|
||||
project: string | undefined;
|
||||
project_detail: IProject | undefined;
|
||||
last_used: boolean | undefined;
|
||||
created_at: Date | undefined;
|
||||
updated_at: Date | undefined;
|
||||
created_by: string | undefined;
|
||||
@ -38,7 +36,9 @@ export interface IEstimate {
|
||||
|
||||
export interface IEstimateFormData {
|
||||
estimate?: {
|
||||
type: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
last_used?: boolean;
|
||||
};
|
||||
estimate_points: {
|
||||
id?: string | undefined;
|
||||
|
@ -59,7 +59,9 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
|
||||
if (validatedEstimatePoints.length === estimatePoints?.length) {
|
||||
const payload: IEstimateFormData = {
|
||||
estimate: {
|
||||
name: ESTIMATE_SYSTEMS[estimateSystem]?.name,
|
||||
type: estimateSystem,
|
||||
last_used: true,
|
||||
},
|
||||
estimate_points: validatedEstimatePoints,
|
||||
};
|
||||
@ -122,6 +124,8 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
|
||||
)}
|
||||
{estimatePoints && (
|
||||
<EstimateCreateStageTwo
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
estimateSystem={estimateSystem}
|
||||
estimatePoints={estimatePoints}
|
||||
handleEstimatePoints={handleUpdatePoints}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { TEstimatePointsObject } from "@plane/types";
|
||||
import { Button, Sortable } from "@plane/ui";
|
||||
@ -8,13 +9,15 @@ import { EstimatePointItem } from "@/components/estimates";
|
||||
import { EEstimateSystem, EEstimateUpdateStages, ESTIMATE_SYSTEMS, maxEstimatesCount } from "@/constants/estimates";
|
||||
|
||||
type TEstimateCreateStageTwo = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimateSystem: EEstimateSystem;
|
||||
estimatePoints: TEstimatePointsObject[];
|
||||
handleEstimatePoints: (value: TEstimatePointsObject[]) => void;
|
||||
};
|
||||
|
||||
export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
|
||||
const { estimateSystem, estimatePoints, handleEstimatePoints } = props;
|
||||
export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = observer((props) => {
|
||||
const { workspaceSlug, projectId, estimateSystem, estimatePoints, handleEstimatePoints } = props;
|
||||
|
||||
const currentEstimateSystem = ESTIMATE_SYSTEMS[estimateSystem] || undefined;
|
||||
|
||||
@ -22,7 +25,7 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
|
||||
const currentEstimationPoints = estimatePoints;
|
||||
const newEstimationPoint: TEstimatePointsObject = {
|
||||
key: currentEstimationPoints.length + 1,
|
||||
value: "0",
|
||||
value: "",
|
||||
};
|
||||
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]);
|
||||
};
|
||||
@ -59,11 +62,15 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
|
||||
data={estimatePoints}
|
||||
render={(value: TEstimatePointsObject, index: number) => (
|
||||
<EstimatePointItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
estimateId={undefined}
|
||||
mode={EEstimateUpdateStages.CREATE}
|
||||
item={value}
|
||||
estimatePoints={estimatePoints}
|
||||
editItem={(value: string) => editEstimationPoint(index, value)}
|
||||
deleteItem={() => deleteEstimationPoint(index)}
|
||||
handleEstimatePoints={handleEstimatePoints}
|
||||
/>
|
||||
)}
|
||||
onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))}
|
||||
@ -77,4 +84,4 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -17,5 +17,4 @@ export * from "./create";
|
||||
export * from "./update";
|
||||
|
||||
// estimate points
|
||||
export * from "./points/estimate-point-item";
|
||||
export * from "./points/inline-editable";
|
||||
export * from "./points";
|
||||
|
95
web/components/estimates/points/create.tsx
Normal file
95
web/components/estimates/points/create.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { EEstimateSystem } from "@/constants/estimates";
|
||||
// hooks
|
||||
import { useEstimate } from "@/hooks/store";
|
||||
|
||||
type TEstimatePointCreate = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimateId: string;
|
||||
estimatePointId: string | undefined;
|
||||
};
|
||||
|
||||
export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) => {
|
||||
const { workspaceSlug, projectId, estimateId, estimatePointId } = props;
|
||||
// hooks
|
||||
const { asJson: estimate, estimatePointIds, creteEstimatePoint } = useEstimate(estimateId);
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
const [estimateValue, setEstimateValue] = useState("");
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (estimatePointId) {
|
||||
if (!workspaceSlug || !projectId || !projectId || !estimatePointIds) return;
|
||||
try {
|
||||
const estimateType: EEstimateSystem | undefined = estimate?.type;
|
||||
let isEstimateValid = false;
|
||||
if (estimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(estimateType)) {
|
||||
if (estimateValue && Number(estimateValue) && Number(estimateValue) >= 0) {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
} else if (estimateType && estimateType === EEstimateSystem.CATEGORIES) {
|
||||
if (estimateValue && estimateValue.length > 0) {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEstimateValid) {
|
||||
setLoader(true);
|
||||
const payload = {
|
||||
key: estimatePointIds?.length + 1,
|
||||
value: estimateValue,
|
||||
};
|
||||
await creteEstimatePoint(workspaceSlug, projectId, payload);
|
||||
setLoader(false);
|
||||
handleClose();
|
||||
} else {
|
||||
console.log("please enter a valid estimate value");
|
||||
}
|
||||
} catch {
|
||||
setLoader(false);
|
||||
console.log("something went wrong. please try again later");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setEstimateValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div className="w-full border border-custom-border-200 rounded">
|
||||
<input
|
||||
type="text"
|
||||
value={estimateValue}
|
||||
onChange={(e) => setEstimateValue(e.target.value)}
|
||||
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
{loader ? (
|
||||
<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="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"
|
||||
onClick={handleCreate}
|
||||
>
|
||||
<Check 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={handleClose}
|
||||
>
|
||||
<X size={14} className="text-custom-text-200" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
100
web/components/estimates/points/delete.tsx
Normal file
100
web/components/estimates/points/delete.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { EEstimateSystem } from "@/constants/estimates";
|
||||
// hooks
|
||||
import { useEstimate, useEstimatePoint } from "@/hooks/store";
|
||||
|
||||
type TEstimatePointDelete = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimateId: string;
|
||||
estimatePointId: string | undefined;
|
||||
};
|
||||
|
||||
export const EstimatePointDelete: FC<TEstimatePointDelete> = observer((props) => {
|
||||
const { workspaceSlug, projectId, estimateId, estimatePointId } = props;
|
||||
// hooks
|
||||
const { asJson: estimate, estimatePointIds } = useEstimate(estimateId);
|
||||
const { asJson: estimatePoint, updateEstimatePoint } = useEstimatePoint(estimateId, estimatePointId);
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
const [estimateValue, setEstimateValue] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (estimateValue === undefined) setEstimateValue(estimatePoint?.value || "");
|
||||
}, [estimateValue, estimatePoint]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (estimatePointId) {
|
||||
if (!workspaceSlug || !projectId || !projectId || !estimatePointIds) return;
|
||||
try {
|
||||
const estimateType: EEstimateSystem | undefined = estimate?.type;
|
||||
let isEstimateValid = false;
|
||||
if (estimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(estimateType)) {
|
||||
if (estimateValue && Number(estimateValue) && Number(estimateValue) >= 0) {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
} else if (estimateType && estimateType === EEstimateSystem.CATEGORIES) {
|
||||
if (estimateValue && estimateValue.length > 0) {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEstimateValid) {
|
||||
setLoader(true);
|
||||
const payload = {
|
||||
key: estimatePointIds?.length + 1,
|
||||
value: estimateValue,
|
||||
};
|
||||
await updateEstimatePoint(workspaceSlug, projectId, payload);
|
||||
setLoader(false);
|
||||
handleClose();
|
||||
} else {
|
||||
console.log("please enter a valid estimate value");
|
||||
}
|
||||
} catch {
|
||||
setLoader(false);
|
||||
console.log("something went wrong. please try again later");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setEstimateValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div className="w-full border border-custom-border-200 rounded">
|
||||
<input
|
||||
type="text"
|
||||
value={estimateValue}
|
||||
onChange={(e) => setEstimateValue(e.target.value)}
|
||||
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
{loader ? (
|
||||
<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="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"
|
||||
onClick={handleCreate}
|
||||
>
|
||||
<Check 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={handleClose}
|
||||
>
|
||||
<X size={14} className="text-custom-text-200" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
29
web/components/estimates/points/edit-root.tsx
Normal file
29
web/components/estimates/points/edit-root.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Draggable } from "@plane/ui";
|
||||
// constants
|
||||
import { EEstimateUpdateStages } from "@/constants/estimates";
|
||||
|
||||
type TEstimatePointEditRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimateId: string;
|
||||
mode: EEstimateUpdateStages;
|
||||
};
|
||||
|
||||
type TEstimatePointEditingState = "update" | "delete";
|
||||
|
||||
export const EstimatePointEditRoot: FC<TEstimatePointEditRoot> = observer((props) => {
|
||||
// props
|
||||
const { workspaceSlug, projectId, estimateId, mode } = props;
|
||||
// hooks
|
||||
// states
|
||||
const [editingState, setEditingState] = useState<TEstimatePointEditingState | undefined>(undefined);
|
||||
|
||||
const [estimateEditLoader, setEstimateEditLoader] = useState(false);
|
||||
const [deletedEstimateValue, setDeletedEstimateValue] = useState<string | undefined>(undefined);
|
||||
const [isEstimateEditing, setIsEstimateEditing] = useState(false);
|
||||
const [isEstimateDeleting, setIsEstimateDeleting] = useState(false);
|
||||
|
||||
return <Draggable data={item}></Draggable>;
|
||||
});
|
@ -8,22 +8,37 @@ import { Draggable, Spinner } from "@plane/ui";
|
||||
import { EEstimateUpdateStages } from "@/constants/estimates";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useEstimate } from "@/hooks/store";
|
||||
import { useEstimate, useEstimatePoint } from "@/hooks/store";
|
||||
|
||||
type TEstimatePointItem = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimateId: string | undefined;
|
||||
mode: EEstimateUpdateStages;
|
||||
item: TEstimatePointsObject;
|
||||
estimatePoints: TEstimatePointsObject[];
|
||||
editItem: (value: string) => void;
|
||||
deleteItem: () => void;
|
||||
handleEstimatePoints: (value: TEstimatePointsObject[]) => void;
|
||||
};
|
||||
|
||||
export const EstimatePointItem: FC<TEstimatePointItem> = observer((props) => {
|
||||
// props
|
||||
const { estimateId, mode, item, editItem, deleteItem } = props;
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
estimateId,
|
||||
mode,
|
||||
item,
|
||||
estimatePoints,
|
||||
editItem,
|
||||
deleteItem,
|
||||
handleEstimatePoints,
|
||||
} = props;
|
||||
const { id, key, value } = item;
|
||||
// hooks
|
||||
const { asJson: estimate, updateEstimate, deleteEstimate } = useEstimate(estimateId);
|
||||
const { asJson: estimate, creteEstimatePoint, deleteEstimatePoint } = useEstimate(estimateId);
|
||||
const { updateEstimatePoint } = useEstimatePoint(estimateId, id);
|
||||
// ref
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
// states
|
||||
@ -35,7 +50,7 @@ export const EstimatePointItem: FC<TEstimatePointItem> = observer((props) => {
|
||||
const [isEstimateDeleting, setIsEstimateDeleting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputValue === undefined || inputValue != value) setInputValue(value);
|
||||
if (value && inputValue === undefined) setInputValue(value);
|
||||
}, [value, inputValue]);
|
||||
|
||||
const handleCreateEdit = (value: string) => {
|
||||
@ -43,11 +58,26 @@ export const EstimatePointItem: FC<TEstimatePointItem> = observer((props) => {
|
||||
editItem(value);
|
||||
};
|
||||
|
||||
const handleNewEstimatePoint = async () => {
|
||||
if (inputValue) {
|
||||
try {
|
||||
setEstimateEditLoader(true);
|
||||
const estimatePoint = await creteEstimatePoint(workspaceSlug, projectId, { key: key, value: inputValue });
|
||||
if (estimatePoint)
|
||||
handleEstimatePoints([...estimatePoints, { id: estimatePoint.id, key: key, value: inputValue }]);
|
||||
setIsEstimateEditing(false);
|
||||
setEstimateEditLoader(false);
|
||||
} catch (error) {
|
||||
setEstimateEditLoader(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = async () => {
|
||||
if (id) {
|
||||
try {
|
||||
setEstimateEditLoader(true);
|
||||
await updateEstimate({ estimate_points: [{ id: id, key: key, value: value }] });
|
||||
await updateEstimatePoint(workspaceSlug, projectId, { key: key, value: inputValue });
|
||||
setIsEstimateEditing(false);
|
||||
setEstimateEditLoader(false);
|
||||
} catch (error) {
|
||||
@ -62,7 +92,7 @@ export const EstimatePointItem: FC<TEstimatePointItem> = observer((props) => {
|
||||
if (id) {
|
||||
try {
|
||||
setEstimateEditLoader(true);
|
||||
await deleteEstimate(deletedEstimateValue);
|
||||
await deleteEstimatePoint(workspaceSlug, projectId, id, deletedEstimateValue);
|
||||
setIsEstimateDeleting(false);
|
||||
setEstimateEditLoader(false);
|
||||
} catch (error) {
|
||||
@ -78,24 +108,65 @@ export const EstimatePointItem: FC<TEstimatePointItem> = observer((props) => {
|
||||
return (
|
||||
<Draggable data={item}>
|
||||
{!id && (
|
||||
<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) => 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"
|
||||
/>
|
||||
<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.CREATE && (
|
||||
<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) => 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"
|
||||
/>
|
||||
<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.EDIT && (
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div className="w-full border border-custom-border-200 rounded">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
className={cn(
|
||||
"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>
|
||||
{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="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"
|
||||
onClick={handleNewEstimatePoint}
|
||||
>
|
||||
<Check 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={handleDelete}
|
||||
>
|
||||
<X size={14} className="text-custom-text-200" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{id && (
|
||||
|
8
web/components/estimates/points/index.ts
Normal file
8
web/components/estimates/points/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from "./estimate-point-item";
|
||||
export * from "./inline-editable";
|
||||
|
||||
export * from "./edit-root";
|
||||
|
||||
export * from "./create";
|
||||
export * from "./update";
|
||||
export * from "./delete";
|
100
web/components/estimates/points/update.tsx
Normal file
100
web/components/estimates/points/update.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { EEstimateSystem } from "@/constants/estimates";
|
||||
// hooks
|
||||
import { useEstimate, useEstimatePoint } from "@/hooks/store";
|
||||
|
||||
type TEstimatePointUpdate = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimateId: string;
|
||||
estimatePointId: string | undefined;
|
||||
};
|
||||
|
||||
export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) => {
|
||||
const { workspaceSlug, projectId, estimateId, estimatePointId } = props;
|
||||
// hooks
|
||||
const { asJson: estimate, estimatePointIds } = useEstimate(estimateId);
|
||||
const { asJson: estimatePoint, updateEstimatePoint } = useEstimatePoint(estimateId, estimatePointId);
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
const [estimateValue, setEstimateValue] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (estimateValue === undefined) setEstimateValue(estimatePoint?.value || "");
|
||||
}, [estimateValue, estimatePoint]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (estimatePointId) {
|
||||
if (!workspaceSlug || !projectId || !projectId || !estimatePointIds) return;
|
||||
try {
|
||||
const estimateType: EEstimateSystem | undefined = estimate?.type;
|
||||
let isEstimateValid = false;
|
||||
if (estimateType && [(EEstimateSystem.TIME, EEstimateSystem.POINTS)].includes(estimateType)) {
|
||||
if (estimateValue && Number(estimateValue) && Number(estimateValue) >= 0) {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
} else if (estimateType && estimateType === EEstimateSystem.CATEGORIES) {
|
||||
if (estimateValue && estimateValue.length > 0) {
|
||||
isEstimateValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEstimateValid) {
|
||||
setLoader(true);
|
||||
const payload = {
|
||||
key: estimatePointIds?.length + 1,
|
||||
value: estimateValue,
|
||||
};
|
||||
await updateEstimatePoint(workspaceSlug, projectId, payload);
|
||||
setLoader(false);
|
||||
handleClose();
|
||||
} else {
|
||||
console.log("please enter a valid estimate value");
|
||||
}
|
||||
} catch {
|
||||
setLoader(false);
|
||||
console.log("something went wrong. please try again later");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setEstimateValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div className="w-full border border-custom-border-200 rounded">
|
||||
<input
|
||||
type="text"
|
||||
value={estimateValue}
|
||||
onChange={(e) => setEstimateValue(e.target.value)}
|
||||
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
{loader ? (
|
||||
<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="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"
|
||||
onClick={handleCreate}
|
||||
>
|
||||
<Check 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={handleClose}
|
||||
>
|
||||
<X size={14} className="text-custom-text-200" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,13 +1,12 @@
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { IEstimateFormData, TEstimatePointsObject, TEstimateUpdateStageKeys, TEstimateSystemKeys } from "@plane/types";
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { TEstimatePointsObject, TEstimateUpdateStageKeys } from "@plane/types";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
|
||||
import { EstimateUpdateStageOne, EstimateUpdateStageTwo } from "@/components/estimates";
|
||||
// constants
|
||||
import { EEstimateSystem } from "@/constants/estimates";
|
||||
// hooks
|
||||
import {
|
||||
useEstimate,
|
||||
@ -26,18 +25,19 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
||||
// props
|
||||
const { workspaceSlug, projectId, estimateId, isOpen, handleClose } = props;
|
||||
// hooks
|
||||
const { asJson: currentEstimate, updateEstimate } = useEstimate(estimateId);
|
||||
const { asJson: currentEstimate } = useEstimate(estimateId);
|
||||
// states
|
||||
const [estimateEditType, setEstimateEditType] = useState<TEstimateUpdateStageKeys | undefined>(undefined);
|
||||
const [estimatePoints, setEstimatePoints] = useState<TEstimatePointsObject[] | undefined>(undefined);
|
||||
|
||||
const handleEstimateEditType = (type: TEstimateUpdateStageKeys) => {
|
||||
if (currentEstimate?.points && currentEstimate?.points.length > 0) {
|
||||
const estimateValidatePoints: TEstimatePointsObject[] = [];
|
||||
let estimateValidatePoints: TEstimatePointsObject[] = [];
|
||||
currentEstimate?.points.map(
|
||||
(point) =>
|
||||
point.key && point.value && estimateValidatePoints.push({ id: point.id, key: point.key, value: point.value })
|
||||
);
|
||||
estimateValidatePoints = orderBy(estimateValidatePoints, ["key"], ["asc"]);
|
||||
if (estimateValidatePoints.length > 0) {
|
||||
setEstimateEditType(type);
|
||||
setEstimatePoints(estimateValidatePoints);
|
||||
@ -56,57 +56,6 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
||||
|
||||
// derived values
|
||||
const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]);
|
||||
const isNewEstimatePointsToCreate =
|
||||
(estimatePoints || []).filter((point) => point.id === undefined).length > 0 ? true : false;
|
||||
|
||||
const handleUpdateEstimate = async () => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId || !estimateId || currentEstimate?.type === undefined) return;
|
||||
|
||||
const currentEstimatePoints = (estimatePoints || []).filter((point) => point.id === undefined);
|
||||
const currentEstimationType: TEstimateSystemKeys = currentEstimate?.type;
|
||||
const validatedEstimatePoints: TEstimatePointsObject[] = [];
|
||||
|
||||
if ([EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(currentEstimationType)) {
|
||||
currentEstimatePoints?.map((estimatePoint) => {
|
||||
if (
|
||||
estimatePoint.value &&
|
||||
((estimatePoint.value != "0" && Number(estimatePoint.value)) || estimatePoint.value === "0")
|
||||
)
|
||||
validatedEstimatePoints.push(estimatePoint);
|
||||
});
|
||||
} else {
|
||||
currentEstimatePoints?.map((estimatePoint) => {
|
||||
if (estimatePoint.value) validatedEstimatePoints.push(estimatePoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (validatedEstimatePoints.length === currentEstimatePoints?.length) {
|
||||
const payload: IEstimateFormData = {
|
||||
estimate_points: validatedEstimatePoints,
|
||||
};
|
||||
await updateEstimate(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Estimate system created",
|
||||
message: "Created and Enabled successfully",
|
||||
});
|
||||
handleClose();
|
||||
} else {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "something went wrong",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "something went wrong",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
||||
@ -135,6 +84,8 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
||||
{!estimateEditType && <EstimateUpdateStageOne handleEstimateEditType={handleEstimateEditType} />}
|
||||
{estimateEditType && estimatePoints && (
|
||||
<EstimateUpdateStageTwo
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
estimate={currentEstimate}
|
||||
estimateEditType={estimateEditType}
|
||||
estimatePoints={estimatePoints}
|
||||
@ -147,11 +98,6 @@ export const UpdateEstimateModal: FC<TUpdateEstimateModal> = observer((props) =>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
{isNewEstimatePointsToCreate && (
|
||||
<Button variant="primary" size="sm" onClick={handleUpdateEstimate}>
|
||||
Update Estimate
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCore>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { FC } from "react";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { observer } from "mobx-react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { IEstimate, TEstimatePointsObject, TEstimateUpdateStageKeys } from "@plane/types";
|
||||
@ -9,6 +10,8 @@ import { EstimatePointItem } from "@/components/estimates";
|
||||
import { EEstimateUpdateStages, maxEstimatesCount } from "@/constants/estimates";
|
||||
|
||||
type TEstimateUpdateStageTwo = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
estimate: IEstimate;
|
||||
estimateEditType: TEstimateUpdateStageKeys | undefined;
|
||||
estimatePoints: TEstimatePointsObject[];
|
||||
@ -16,15 +19,15 @@ type TEstimateUpdateStageTwo = {
|
||||
};
|
||||
|
||||
export const EstimateUpdateStageTwo: FC<TEstimateUpdateStageTwo> = observer((props) => {
|
||||
const { estimate, estimateEditType, estimatePoints, handleEstimatePoints } = props;
|
||||
const { workspaceSlug, projectId, estimate, estimateEditType, estimatePoints, handleEstimatePoints } = props;
|
||||
|
||||
const currentEstimateSystem = estimate || undefined;
|
||||
|
||||
const addNewEstimationPoint = () => {
|
||||
const currentEstimationPoints = estimatePoints;
|
||||
const currentEstimationPoints = cloneDeep(estimatePoints);
|
||||
const newEstimationPoint: TEstimatePointsObject = {
|
||||
key: currentEstimationPoints.length + 1,
|
||||
value: "0",
|
||||
value: "",
|
||||
};
|
||||
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]);
|
||||
};
|
||||
@ -64,11 +67,15 @@ export const EstimateUpdateStageTwo: FC<TEstimateUpdateStageTwo> = observer((pro
|
||||
data={estimatePoints}
|
||||
render={(value: TEstimatePointsObject, index: number) => (
|
||||
<EstimatePointItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
estimateId={estimate?.id || undefined}
|
||||
mode={estimateEditType}
|
||||
item={value}
|
||||
estimatePoints={estimatePoints}
|
||||
editItem={(value: string) => editEstimationPoint(index, value)}
|
||||
deleteItem={() => deleteEstimationPoint(index)}
|
||||
handleEstimatePoints={handleEstimatePoints}
|
||||
/>
|
||||
)}
|
||||
onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// types
|
||||
import { IEstimate, IEstimateFormData } from "@plane/types";
|
||||
import { IEstimate, IEstimateFormData, IEstimatePoint } from "@plane/types";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
// services
|
||||
@ -10,7 +10,6 @@ export class EstimateService extends APIService {
|
||||
super(API_BASE_URL);
|
||||
}
|
||||
|
||||
// fetching the estimates in workspace level
|
||||
async fetchWorkspaceEstimates(workspaceSlug: string): Promise<IEstimate[] | undefined> {
|
||||
try {
|
||||
const { data } = await this.get(`/api/workspaces/${workspaceSlug}/estimates/`);
|
||||
@ -61,7 +60,7 @@ export class EstimateService extends APIService {
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimateId: string,
|
||||
payload: IEstimateFormData
|
||||
payload: Partial<IEstimate>
|
||||
): Promise<IEstimate | undefined> {
|
||||
try {
|
||||
const { data } = await this.patch(
|
||||
@ -74,16 +73,51 @@ export class EstimateService extends APIService {
|
||||
}
|
||||
}
|
||||
|
||||
async createEstimatePoint(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimateId: string,
|
||||
payload: Partial<IEstimatePoint>
|
||||
): Promise<IEstimatePoint | undefined> {
|
||||
try {
|
||||
const { data } = await this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-points/`,
|
||||
payload
|
||||
);
|
||||
return data || undefined;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateEstimatePoint(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimateId: string,
|
||||
estimatePointId: string,
|
||||
payload: Partial<IEstimatePoint>
|
||||
): Promise<IEstimatePoint | undefined> {
|
||||
try {
|
||||
const { data } = await this.post(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-points/${estimatePointId}/`,
|
||||
payload
|
||||
);
|
||||
return data || undefined;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async removeEstimatePoint(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimateId: string,
|
||||
estimatePointId: string,
|
||||
payload: { new_estimate_id: string | undefined }
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-point/${estimatePointId}/`,
|
||||
payload
|
||||
params?: { new_estimate_id: string | undefined }
|
||||
): Promise<void> {
|
||||
return this.delete(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/estimate-points/${estimatePointId}/`,
|
||||
params
|
||||
)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { IEstimateFormData, IEstimate, IEstimatePoint as IEstimatePointType } from "@plane/types";
|
||||
import set from "lodash/set";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { IEstimate, IEstimatePoint as IEstimatePointType } from "@plane/types";
|
||||
// services
|
||||
import { EstimateService } from "@/services/project/estimate.service";
|
||||
// store
|
||||
@ -16,7 +17,11 @@ export interface IEstimatePoint extends IEstimatePointType {
|
||||
// computed
|
||||
asJson: IEstimatePointType;
|
||||
// actions
|
||||
updateEstimatePoint: (payload: IEstimateFormData) => Promise<void>;
|
||||
updateEstimatePoint: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
payload: Partial<IEstimatePointType>
|
||||
) => Promise<IEstimatePointType | undefined>;
|
||||
}
|
||||
|
||||
export class EstimatePoint implements IEstimatePoint {
|
||||
@ -73,7 +78,6 @@ export class EstimatePoint implements IEstimatePoint {
|
||||
this.updated_at = this.data.updated_at;
|
||||
this.created_by = this.data.created_by;
|
||||
this.updated_by = this.data.updated_by;
|
||||
|
||||
// service
|
||||
this.service = new EstimateService();
|
||||
}
|
||||
@ -96,10 +100,36 @@ export class EstimatePoint implements IEstimatePoint {
|
||||
}
|
||||
|
||||
// actions
|
||||
updateEstimatePoint = async (payload: IEstimateFormData) => {
|
||||
/**
|
||||
* @description updating an estimate point
|
||||
* @param { Partial<IEstimatePointType> } payload
|
||||
* @returns { IEstimatePointType | undefined }
|
||||
*/
|
||||
updateEstimatePoint = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
payload: Partial<IEstimatePointType>
|
||||
): Promise<IEstimatePointType | undefined> => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
if (!workspaceSlug || !projectId || !this.projectEstimate?.id || !this.id || !payload) return undefined;
|
||||
if (!this.projectEstimate?.id || !this.id || !payload) return undefined;
|
||||
|
||||
const estimatePoint = await this.service.updateEstimatePoint(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
this.projectEstimate?.id,
|
||||
this.id,
|
||||
payload
|
||||
);
|
||||
if (estimatePoint) {
|
||||
runInAction(() => {
|
||||
Object.keys(payload).map((key) => {
|
||||
const estimatePointKey = key as keyof IEstimatePointType;
|
||||
set(this, estimatePointKey, estimatePoint[estimatePointKey]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return estimatePoint;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -1,16 +1,8 @@
|
||||
import set from "lodash/set";
|
||||
import unset from "lodash/unset";
|
||||
import update from "lodash/update";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
import {
|
||||
IEstimate as IEstimateType,
|
||||
IEstimatePoint as IEstimatePointType,
|
||||
IProject,
|
||||
IWorkspace,
|
||||
TEstimateSystemKeys,
|
||||
IEstimateFormData,
|
||||
} from "@plane/types";
|
||||
import { IEstimate as IEstimateType, IEstimatePoint as IEstimatePointType, TEstimateSystemKeys } from "@plane/types";
|
||||
// services
|
||||
import { EstimateService } from "@/services/project/estimate.service";
|
||||
// store
|
||||
@ -31,8 +23,22 @@ export interface IEstimate extends IEstimateType {
|
||||
estimatePointIds: string[] | undefined;
|
||||
estimatePointById: (estimateId: string) => IEstimatePointType | undefined;
|
||||
// actions
|
||||
updateEstimate: (payload: IEstimateFormData) => Promise<void>;
|
||||
deleteEstimate: (estimatePointId: string | undefined) => Promise<void>;
|
||||
updateEstimate: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
payload: Partial<IEstimateType>
|
||||
) => Promise<IEstimateType | undefined>;
|
||||
creteEstimatePoint: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
payload: Partial<IEstimatePointType>
|
||||
) => Promise<IEstimatePointType | undefined>;
|
||||
deleteEstimatePoint: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimatePointId: string,
|
||||
newEstimatePointId: string | undefined
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export class Estimate implements IEstimate {
|
||||
@ -43,9 +49,8 @@ export class Estimate implements IEstimate {
|
||||
type: TEstimateSystemKeys | undefined = undefined;
|
||||
points: IEstimatePointType[] | undefined = undefined;
|
||||
workspace: string | undefined = undefined;
|
||||
workspace_detail: IWorkspace | undefined = undefined;
|
||||
project: string | undefined = undefined;
|
||||
project_detail: IProject | undefined = undefined;
|
||||
last_used: boolean | undefined = undefined;
|
||||
created_at: Date | undefined = undefined;
|
||||
updated_at: Date | undefined = undefined;
|
||||
created_by: string | undefined = undefined;
|
||||
@ -68,9 +73,8 @@ export class Estimate implements IEstimate {
|
||||
type: observable.ref,
|
||||
points: observable,
|
||||
workspace: observable.ref,
|
||||
workspace_detail: observable,
|
||||
project: observable.ref,
|
||||
project_detail: observable,
|
||||
last_used: observable.ref,
|
||||
created_at: observable.ref,
|
||||
updated_at: observable.ref,
|
||||
created_by: observable.ref,
|
||||
@ -83,7 +87,8 @@ export class Estimate implements IEstimate {
|
||||
estimatePointIds: computed,
|
||||
// actions
|
||||
updateEstimate: action,
|
||||
deleteEstimate: action,
|
||||
creteEstimatePoint: action,
|
||||
deleteEstimatePoint: action,
|
||||
});
|
||||
this.id = this.data.id;
|
||||
this.name = this.data.name;
|
||||
@ -91,14 +96,12 @@ export class Estimate implements IEstimate {
|
||||
this.type = this.data.type;
|
||||
this.points = this.data.points;
|
||||
this.workspace = this.data.workspace;
|
||||
this.workspace_detail = this.data.workspace_detail;
|
||||
this.project = this.data.project;
|
||||
this.project_detail = this.data.project_detail;
|
||||
this.last_used = this.data.last_used;
|
||||
this.created_at = this.data.created_at;
|
||||
this.updated_at = this.data.updated_at;
|
||||
this.created_by = this.data.created_by;
|
||||
this.updated_by = this.data.updated_by;
|
||||
|
||||
this.data.points?.forEach((estimationPoint) => {
|
||||
if (estimationPoint.id)
|
||||
set(this.estimatePoints, [estimationPoint.id], new EstimatePoint(this.store, this.data, estimationPoint));
|
||||
@ -116,9 +119,8 @@ export class Estimate implements IEstimate {
|
||||
type: this.type,
|
||||
points: this.points,
|
||||
workspace: this.workspace,
|
||||
workspace_detail: this.workspace_detail,
|
||||
project: this.project,
|
||||
project_detail: this.project_detail,
|
||||
last_used: this.last_used,
|
||||
created_at: this.created_at,
|
||||
updated_at: this.updated_at,
|
||||
created_by: this.created_by,
|
||||
@ -143,35 +145,95 @@ export class Estimate implements IEstimate {
|
||||
});
|
||||
|
||||
// actions
|
||||
updateEstimate = async (payload: IEstimateFormData) => {
|
||||
/**
|
||||
* @description update an estimate
|
||||
* @param { string } workspaceSlug
|
||||
* @param { string } projectId
|
||||
* @param { Partial<IEstimateType> } payload
|
||||
* @returns { IEstimateType | undefined }
|
||||
*/
|
||||
updateEstimate = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
payload: Partial<IEstimateType>
|
||||
): Promise<IEstimateType | undefined> => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
if (!workspaceSlug || !projectId || !this.id || !payload) return;
|
||||
if (!this.id || !payload) return;
|
||||
|
||||
await this.service.updateEstimate(workspaceSlug, projectId, this.id, payload);
|
||||
const estimate = await this.service.updateEstimate(workspaceSlug, projectId, this.id, payload);
|
||||
if (estimate) {
|
||||
runInAction(() => {
|
||||
Object.keys(payload).map((key) => {
|
||||
const estimateKey = key as keyof IEstimateType;
|
||||
set(this, estimateKey, estimate[estimateKey]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// runInAction(() => {
|
||||
// this.points = payload.estimate_points;
|
||||
// this.data.points = payload.estimate_points;
|
||||
// });
|
||||
return estimate;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
deleteEstimate = async (estimatePointId: string | undefined) => {
|
||||
/**
|
||||
* @description create an estimate point
|
||||
* @param { string } workspaceSlug
|
||||
* @param { string } projectId
|
||||
* @param { Partial<IEstimatePointType> } payload
|
||||
* @returns { IEstimatePointType | undefined }
|
||||
*/
|
||||
creteEstimatePoint = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
payload: Partial<IEstimatePointType>
|
||||
): Promise<IEstimatePointType | undefined> => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
if (!workspaceSlug || !projectId || !estimatePointId) return;
|
||||
if (!this.id || !payload) return;
|
||||
|
||||
// make delete estimation request
|
||||
const estimatePoint = await this.service.createEstimatePoint(workspaceSlug, projectId, this.id, payload);
|
||||
if (estimatePoint) {
|
||||
runInAction(() => {
|
||||
if (estimatePoint.id) {
|
||||
set(this.estimatePoints, [estimatePoint.id], new EstimatePoint(this.store, this.data, estimatePoint));
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description delete an estimate point
|
||||
* @param { string } workspaceSlug
|
||||
* @param { string } projectId
|
||||
* @param { string } estimatePointId
|
||||
* @param { string | undefined } newEstimatePointId
|
||||
* @returns { void }
|
||||
*/
|
||||
deleteEstimatePoint = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
estimatePointId: string,
|
||||
newEstimatePointId: string | undefined
|
||||
) => {
|
||||
try {
|
||||
if (!this.id) return;
|
||||
|
||||
const deleteEstimatePoint = await this.service.removeEstimatePoint(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
this.id,
|
||||
estimatePointId,
|
||||
newEstimatePointId ? { new_estimate_id: newEstimatePointId } : undefined
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
update(this, "points", (estimationPoints = []) =>
|
||||
estimationPoints.filter((point: IEstimatePointType) => point.id !== estimatePointId)
|
||||
);
|
||||
unset(this.estimatePoints, [estimatePointId]);
|
||||
});
|
||||
|
||||
return deleteEstimatePoint;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import orderBy from "lodash/orderBy";
|
||||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import update from "lodash/update";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
@ -59,7 +59,6 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
// computed
|
||||
currentActiveEstimateId: computed,
|
||||
archivedEstimateIds: computed,
|
||||
projectEstimateIds: computed,
|
||||
// actions
|
||||
getWorkspaceEstimates: action,
|
||||
getProjectEstimates: action,
|
||||
@ -79,9 +78,10 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
get currentActiveEstimateId(): string | undefined {
|
||||
const { projectId } = this.store.router;
|
||||
if (!projectId) return undefined;
|
||||
const projectDetails = this.store.projectRoot.project.getProjectById(projectId);
|
||||
if (!projectDetails) return undefined;
|
||||
return projectDetails.estimate ?? undefined;
|
||||
const currentActiveEstimateId = Object.values(this.estimates || {}).find(
|
||||
(p) => p.project === projectId && p.last_used
|
||||
);
|
||||
return currentActiveEstimateId?.id ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,25 +91,15 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
get archivedEstimateIds(): string[] | undefined {
|
||||
const { projectId } = this.store.router;
|
||||
if (!projectId) return undefined;
|
||||
const archivedEstimateIds = Object.values(this.estimates || {})
|
||||
.filter((p) => p.project === projectId && p.id !== this.currentActiveEstimateId)
|
||||
.map((p) => p.id) as string[];
|
||||
const archivedEstimates = orderBy(
|
||||
Object.values(this.estimates || {}).filter((p) => p.project === projectId && !p.last_used),
|
||||
["created_at"],
|
||||
"desc"
|
||||
);
|
||||
const archivedEstimateIds = archivedEstimates.map((p) => p.id) as string[];
|
||||
return archivedEstimateIds ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get all estimate ids for a project
|
||||
* @returns { string[] | undefined }
|
||||
*/
|
||||
get projectEstimateIds(): string[] | undefined {
|
||||
const { projectId } = this.store.router;
|
||||
if (!projectId) return undefined;
|
||||
const projectEstimatesIds = Object.values(this.estimates || {})
|
||||
.filter((p) => p.project === projectId)
|
||||
.map((p) => p.id) as string[];
|
||||
return projectEstimatesIds ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get estimates are enabled in the project or not
|
||||
* @returns { boolean }
|
||||
@ -145,6 +135,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
// actions
|
||||
/**
|
||||
* @description fetch all estimates for a workspace
|
||||
* @param { string } workspaceSlug
|
||||
* @returns { IEstimateType[] | undefined }
|
||||
*/
|
||||
getWorkspaceEstimates = async (
|
||||
@ -153,7 +144,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
): Promise<IEstimateType[] | undefined> => {
|
||||
try {
|
||||
this.error = undefined;
|
||||
if (!this.projectEstimateIds) this.loader = loader ? loader : "init-loader";
|
||||
if (Object.keys(this.estimates || {}).length <= 0) this.loader = loader ? loader : "init-loader";
|
||||
|
||||
const estimates = await this.service.fetchWorkspaceEstimates(workspaceSlug);
|
||||
if (estimates && estimates.length > 0) {
|
||||
@ -176,6 +167,8 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
|
||||
/**
|
||||
* @description fetch all estimates for a project
|
||||
* @param { string } workspaceSlug
|
||||
* @param { string } projectId
|
||||
* @returns { IEstimateType[] | undefined }
|
||||
*/
|
||||
getProjectEstimates = async (
|
||||
@ -185,7 +178,7 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
): Promise<IEstimateType[] | undefined> => {
|
||||
try {
|
||||
this.error = undefined;
|
||||
if (!this.projectEstimateIds) this.loader = loader ? loader : "init-loader";
|
||||
if (!this.estimateIdsByProjectId(projectId)) this.loader = loader ? loader : "init-loader";
|
||||
|
||||
const estimates = await this.service.fetchProjectEstimates(workspaceSlug, projectId);
|
||||
if (estimates && estimates.length > 0) {
|
||||
@ -208,6 +201,8 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
|
||||
/**
|
||||
* @description update an estimate for a project
|
||||
* @param { string } workspaceSlug
|
||||
* @param { string } projectId
|
||||
* @param { string } estimateId
|
||||
* @returns IEstimateType | undefined
|
||||
*/
|
||||
@ -241,7 +236,9 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
|
||||
/**
|
||||
* @description create an estimate for a project
|
||||
* @param { Partial<IEstimateType> } payload
|
||||
* @param { string } workspaceSlug
|
||||
* @param { string } projectId
|
||||
* @param { Partial<IEstimateFormData> } payload
|
||||
* @returns
|
||||
*/
|
||||
createEstimate = async (
|
||||
@ -253,21 +250,15 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
|
||||
this.error = undefined;
|
||||
|
||||
const estimate = await this.service.createEstimate(workspaceSlug, projectId, payload);
|
||||
// FIXME: i am getting different response from the server and once backend changes remove the get request and uncomment the commented code
|
||||
let estimates = await this.getProjectEstimates(workspaceSlug, projectId, "mutation-loader");
|
||||
estimates = sortBy(estimates, "created_at");
|
||||
if (estimates && estimates.length > 0)
|
||||
console.log("estimate", estimate);
|
||||
if (estimate) {
|
||||
await this.store.projectRoot.project.updateProject(workspaceSlug, projectId, {
|
||||
estimate: estimates[estimates.length - 1].id,
|
||||
estimate: estimate.id,
|
||||
});
|
||||
// if (estimate) {
|
||||
// await this.store.projectRoot.project.updateProject(workspaceSlug, projectId, {
|
||||
// estimate: estimate.id,
|
||||
// });
|
||||
// runInAction(() => {
|
||||
// if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
|
||||
// });
|
||||
// }
|
||||
runInAction(() => {
|
||||
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
|
||||
});
|
||||
}
|
||||
|
||||
return estimate;
|
||||
} catch (error) {
|
||||
|
Loading…
Reference in New Issue
Block a user