forked from github/plane
feat: estimates (#783)
* chore: use estimate points hook created * chore: user auth layer * fix: build error * chore: estimate crud and validation * fix: build errors --------- Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
parent
d5c2965946
commit
dfa3a7b78d
@ -28,9 +28,7 @@ export const NotAWorkspaceMember = () => {
|
|||||||
<div className="flex items-center gap-2 justify-center">
|
<div className="flex items-center gap-2 justify-center">
|
||||||
<Link href="/invitations">
|
<Link href="/invitations">
|
||||||
<a>
|
<a>
|
||||||
<SecondaryButton onClick={() => router.back()}>
|
<SecondaryButton>Check pending invites</SecondaryButton>
|
||||||
Check pending invites
|
|
||||||
</SecondaryButton>
|
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/create-workspace">
|
<Link href="/create-workspace">
|
||||||
|
@ -20,6 +20,7 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
|||||||
import { Properties } from "types";
|
import { Properties } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
||||||
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
|
|
||||||
export const IssuesFilterView: React.FC = () => {
|
export const IssuesFilterView: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -45,6 +46,8 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
projectId as string
|
projectId as string
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { isEstimateActive } = useEstimateOption();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-x-1">
|
<div className="flex items-center gap-x-1">
|
||||||
@ -233,7 +236,10 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
<h4 className="text-sm text-gray-600">Display Properties</h4>
|
<h4 className="text-sm text-gray-600">Display Properties</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{Object.keys(properties).map((key) => (
|
{Object.keys(properties).map((key) => {
|
||||||
|
if (key === "estimate" && !isEstimateActive) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
type="button"
|
type="button"
|
||||||
@ -246,7 +252,8 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@ import useToast from "hooks/use-toast";
|
|||||||
import {
|
import {
|
||||||
ViewAssigneeSelect,
|
ViewAssigneeSelect,
|
||||||
ViewDueDateSelect,
|
ViewDueDateSelect,
|
||||||
|
ViewEstimateSelect,
|
||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues/view-select";
|
} from "components/issues/view-select";
|
||||||
@ -273,6 +274,14 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
isNotAllowed={isNotAllowed}
|
isNotAllowed={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{properties.estimate && (
|
||||||
|
<ViewEstimateSelect
|
||||||
|
issue={issue}
|
||||||
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
|
position="right"
|
||||||
|
isNotAllowed={isNotAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{type && !isNotAllowed && (
|
{type && !isNotAllowed && (
|
||||||
<CustomMenu width="auto" ellipsis>
|
<CustomMenu width="auto" ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={editIssue}>
|
<CustomMenu.MenuItem onClick={editIssue}>
|
||||||
|
@ -21,10 +21,9 @@ import { IEstimate } from "types";
|
|||||||
import { ESTIMATES_LIST } from "constants/fetch-keys";
|
import { ESTIMATES_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
data?: IEstimate;
|
data?: IEstimate;
|
||||||
isOpen: boolean;
|
|
||||||
isCreate: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IEstimate> = {
|
const defaultValues: Partial<IEstimate> = {
|
||||||
@ -32,7 +31,7 @@ const defaultValues: Partial<IEstimate> = {
|
|||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data, isOpen, isCreate }) => {
|
export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data, isOpen }) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
@ -109,12 +108,11 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data && isCreate) return;
|
|
||||||
reset({
|
reset({
|
||||||
...defaultValues,
|
...defaultValues,
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
}, [data, reset, isCreate]);
|
}, [data, reset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -148,7 +146,9 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
|
|||||||
onSubmit={data ? handleSubmit(updateEstimate) : handleSubmit(createEstimate)}
|
onSubmit={data ? handleSubmit(updateEstimate) : handleSubmit(createEstimate)}
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-2xl font-medium">Create Estimate</div>
|
<div className="text-lg font-medium leading-6">
|
||||||
|
{data ? "Update" : "Create"} Estimate
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
|
@ -18,6 +18,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
|||||||
import type { IEstimate, IEstimatePoint } from "types";
|
import type { IEstimate, IEstimatePoint } from "types";
|
||||||
|
|
||||||
import estimatesService from "services/estimates.service";
|
import estimatesService from "services/estimates.service";
|
||||||
|
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -33,8 +34,6 @@ interface FormValues {
|
|||||||
value4: string;
|
value4: string;
|
||||||
value5: string;
|
value5: string;
|
||||||
value6: string;
|
value6: string;
|
||||||
value7: string;
|
|
||||||
value8: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultValues: FormValues = {
|
const defaultValues: FormValues = {
|
||||||
@ -44,8 +43,6 @@ const defaultValues: FormValues = {
|
|||||||
value4: "",
|
value4: "",
|
||||||
value5: "",
|
value5: "",
|
||||||
value6: "",
|
value6: "",
|
||||||
value7: "",
|
|
||||||
value8: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, onClose }) => {
|
export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, onClose }) => {
|
||||||
@ -56,7 +53,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors, isSubmitting },
|
formState: { isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
} = useForm<FormValues>({ defaultValues });
|
} = useForm<FormValues>({ defaultValues });
|
||||||
@ -95,14 +92,6 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
key: 5,
|
key: 5,
|
||||||
value: formData.value6,
|
value: formData.value6,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 6,
|
|
||||||
value: formData.value7,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 7,
|
|
||||||
value: formData.value8,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,49 +115,20 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateEstimatePoints = async (formData: FormValues) => {
|
const updateEstimatePoints = async (formData: FormValues) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId || !data || data.length === 0) return;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
estimate_points: [
|
estimate_points: data.map((d, index) => ({
|
||||||
{
|
id: d.id,
|
||||||
key: 0,
|
value: (formData as any)[`value${index + 1}`],
|
||||||
value: formData.value1,
|
})),
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 1,
|
|
||||||
value: formData.value2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 2,
|
|
||||||
value: formData.value3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 3,
|
|
||||||
value: formData.value4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 4,
|
|
||||||
value: formData.value5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 5,
|
|
||||||
value: formData.value6,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 6,
|
|
||||||
value: formData.value7,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 7,
|
|
||||||
value: formData.value8,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await estimatesService
|
await estimatesService
|
||||||
.updateEstimatesPoints(
|
.patchEstimatePoints(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
estimate?.id as string,
|
estimate?.id as string,
|
||||||
data?.[0]?.id as string,
|
|
||||||
payload
|
payload
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -183,15 +143,27 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (formData: FormValues) => {
|
||||||
|
if (data && data.length !== 0) await updateEstimatePoints(formData);
|
||||||
|
else await createEstimatePoints(formData);
|
||||||
|
|
||||||
|
if (estimate) mutate(ESTIMATE_POINTS_LIST(estimate.id));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!data) return
|
if (!data || data.length < 6) return;
|
||||||
|
|
||||||
reset({
|
reset({
|
||||||
...defaultValues,
|
...defaultValues,
|
||||||
...data,
|
value1: data[0].value,
|
||||||
|
value2: data[1].value,
|
||||||
|
value3: data[2].value,
|
||||||
|
value4: data[3].value,
|
||||||
|
value5: data[4].value,
|
||||||
|
value6: data[5].value,
|
||||||
});
|
});
|
||||||
}, [data, reset]);
|
}, [data, reset]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={() => handleClose()}>
|
<Dialog as="div" className="relative z-20" onClose={() => handleClose()}>
|
||||||
@ -219,18 +191,16 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform rounded-lg bg-white px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
<Dialog.Panel className="relative transform rounded-lg bg-white px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
||||||
<form
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
onSubmit={
|
|
||||||
data ? handleSubmit(updateEstimatePoints) : handleSubmit(createEstimatePoints)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<h4 className="text-2xl font-medium">Create Estimate Points</h4>
|
<h4 className="text-lg font-medium leading-6">
|
||||||
<div className="grid grid-cols-4 gap-3">
|
{data ? "Update" : "Create"} Estimate Points
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V0</span>
|
<span className="px-2 rounded-lg text-sm text-gray-600">1</span>
|
||||||
<span className="bg-white rounded-lg">
|
<span className="bg-white rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
@ -243,7 +213,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
required: "value is required",
|
required: "value is required",
|
||||||
maxLength: {
|
maxLength: {
|
||||||
value: 10,
|
value: 10,
|
||||||
message: "value should be less than 10 characters",
|
message: "Name should be less than 10 characters",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -252,7 +222,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V1</span>
|
<span className="px-2 rounded-lg text-sm text-gray-600">2</span>
|
||||||
<span className="bg-white rounded-lg">
|
<span className="bg-white rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
@ -274,7 +244,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V2</span>
|
<span className="px-2 rounded-lg text-sm text-gray-600">3</span>
|
||||||
<span className="bg-white rounded-lg">
|
<span className="bg-white rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
@ -296,7 +266,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V3</span>
|
<span className="px-2 rounded-lg text-sm text-gray-600">4</span>
|
||||||
<span className="bg-white rounded-lg">
|
<span className="bg-white rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
@ -318,7 +288,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V4</span>
|
<span className="px-2 rounded-lg text-sm text-gray-600">5</span>
|
||||||
<span className="bg-white rounded-lg">
|
<span className="bg-white rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
@ -340,7 +310,7 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V5</span>
|
<span className="px-2 rounded-lg text-sm text-gray-600">6</span>
|
||||||
<span className="bg-white rounded-lg">
|
<span className="bg-white rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
@ -360,50 +330,6 @@ export const EstimatePointsModal: React.FC<Props> = ({ isOpen, data, estimate, o
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V6</span>
|
|
||||||
<span className="bg-white rounded-lg">
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
name="value7"
|
|
||||||
type="name"
|
|
||||||
placeholder="Value"
|
|
||||||
autoComplete="off"
|
|
||||||
register={register}
|
|
||||||
validations={{
|
|
||||||
required: "value is required",
|
|
||||||
maxLength: {
|
|
||||||
value: 10,
|
|
||||||
message: "Name should be less than 10 characters",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="bg-gray-100 h-full flex items-center rounded-lg">
|
|
||||||
<span className="pl-2 pr-1 rounded-lg text-sm text-gray-600">V7</span>
|
|
||||||
<span className="bg-white rounded-lg">
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
name="value8"
|
|
||||||
type="name"
|
|
||||||
placeholder="Value"
|
|
||||||
autoComplete="off"
|
|
||||||
register={register}
|
|
||||||
validations={{
|
|
||||||
required: "value is required",
|
|
||||||
maxLength: {
|
|
||||||
value: 20,
|
|
||||||
message: "Name should be less than 20 characters",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
import { IEstimate, IProject } from "types";
|
import { IEstimate, IProject } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
||||||
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
estimate: IEstimate;
|
estimate: IEstimate;
|
||||||
@ -88,6 +89,7 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
isOpen={isEstimatePointsModalOpen}
|
isOpen={isEstimatePointsModalOpen}
|
||||||
estimate={estimate}
|
estimate={estimate}
|
||||||
onClose={() => setIsEstimatePointsModalOpen(false)}
|
onClose={() => setIsEstimatePointsModalOpen(false)}
|
||||||
|
data={estimatePoints ? orderArrayBy(estimatePoints, "key") : undefined}
|
||||||
/>
|
/>
|
||||||
<div className="gap-2 py-3">
|
<div className="gap-2 py-3">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
@ -105,7 +107,7 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<CustomMenu ellipsis>
|
<CustomMenu ellipsis>
|
||||||
{projectDetails?.estimate && projectDetails?.estimate !== estimate.id && (
|
{projectDetails?.estimate !== estimate.id && (
|
||||||
<CustomMenu.MenuItem onClick={handleUseEstimate}>
|
<CustomMenu.MenuItem onClick={handleUseEstimate}>
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<SquaresPlusIcon className="h-3.5 w-3.5" />
|
<SquaresPlusIcon className="h-3.5 w-3.5" />
|
||||||
@ -116,7 +118,9 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
<CustomMenu.MenuItem onClick={() => setIsEstimatePointsModalOpen(true)}>
|
<CustomMenu.MenuItem onClick={() => setIsEstimatePointsModalOpen(true)}>
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<ListBulletIcon className="h-3.5 w-3.5" />
|
<ListBulletIcon className="h-3.5 w-3.5" />
|
||||||
<span>{estimatePoints?.length === 8 ? "Update points" : "Create points"}</span>
|
<span>
|
||||||
|
{estimatePoints && estimatePoints?.length > 0 ? "Edit points" : "Create points"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
@ -129,6 +133,7 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
<span>Edit estimate</span>
|
<span>Edit estimate</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
|
{projectDetails?.estimate !== estimate.id && (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleEstimateDelete(estimate.id);
|
handleEstimateDelete(estimate.id);
|
||||||
@ -139,22 +144,23 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
<span>Delete estimate</span>
|
<span>Delete estimate</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
|
)}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
{estimatePoints && estimatePoints.length > 0 ? (
|
{estimatePoints && estimatePoints.length > 0 ? (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 text-sm text-gray-400">
|
||||||
{estimatePoints.length > 0 && "Estimate points ("}
|
Estimate points(
|
||||||
{estimatePoints.map((point, i) => (
|
{estimatePoints.map((point, index) => (
|
||||||
<h6 key={point.id}>
|
<h6 key={point.id}>
|
||||||
{point.value}
|
{point.value}
|
||||||
{i !== estimatePoints.length - 1 && ","}{" "}
|
{index !== estimatePoints.length - 1 && ","}{" "}
|
||||||
</h6>
|
</h6>
|
||||||
))}
|
))}
|
||||||
{estimatePoints.length > 0 && ")"}
|
)
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<p className=" text-sm text-gray-300">No estimate points</p>
|
<p className="text-sm text-gray-400">No estimate points</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -405,7 +405,7 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name="estimate_point"
|
name="estimate_point"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<IssueEstimateSelect chevron={false} value={value} onChange={onChange} />
|
<IssueEstimateSelect value={value} onChange={onChange} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,72 +1,37 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
// services
|
|
||||||
import projectService from "services/project.service";
|
|
||||||
import estimatesService from "services/estimates.service";
|
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "components/ui";
|
import { CustomSelect } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { PlayIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
|
import { PlayIcon } from "@heroicons/react/24/outline";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATE_POINTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: number;
|
value: number;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
chevron: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange, chevron }) => {
|
export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
|
||||||
const router = useRouter();
|
const { isEstimateActive, estimatePoints } = useEstimateOption();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR(
|
if (!isEstimateActive) return null;
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () => projectService.getProject(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: estimatePoints } = useSWR(
|
|
||||||
workspaceSlug && projectId && projectDetails && projectDetails.estimate
|
|
||||||
? ESTIMATE_POINTS_LIST(projectDetails.estimate)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && projectDetails && projectDetails.estimate
|
|
||||||
? () =>
|
|
||||||
estimatesService.getEstimatesPointsList(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
projectDetails.estimate
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
value={value}
|
value={value}
|
||||||
label={
|
label={
|
||||||
<div className="flex items-center gap-2 text-xs min-w-[calc(100%+10px)]">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<span>
|
|
||||||
<PlayIcon className="h-4 w-4 text-gray-700 -rotate-90" />
|
<PlayIcon className="h-4 w-4 text-gray-700 -rotate-90" />
|
||||||
</span>
|
|
||||||
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
||||||
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"}
|
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"}
|
||||||
</span>
|
</span>
|
||||||
{chevron && (
|
|
||||||
<span className="w-full flex justify-end pr-3">
|
|
||||||
<ChevronDownIcon className="h-[9px] w-[9px] text-black" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
position="right"
|
position="right"
|
||||||
width="w-full min-w-[111px]"
|
width="w-full min-w-[6rem]"
|
||||||
noChevron={!chevron}
|
noChevron
|
||||||
>
|
>
|
||||||
{estimatePoints &&
|
{estimatePoints &&
|
||||||
estimatePoints.map((point) => (
|
estimatePoints.map((point) => (
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { IssueEstimateSelect } from "components/issues/select";
|
import { CustomSelect } from "components/ui";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { BanknotesIcon } from "@heroicons/react/24/outline";
|
import { BanknotesIcon, PlayIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { UserAuth } from "types";
|
import { UserAuth } from "types";
|
||||||
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -16,19 +15,47 @@ type Props = {
|
|||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, userAuth }) => {
|
export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, userAuth }) => {
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
||||||
|
|
||||||
|
const { isEstimateActive, estimatePoints } = useEstimateOption();
|
||||||
|
|
||||||
|
if (!isEstimateActive) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center py-2">
|
<div className="flex flex-wrap items-center py-2">
|
||||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||||
<BanknotesIcon className="h-4 w-4 flex-shrink-0" />
|
<PlayIcon className="h-4 w-4 -rotate-90 flex-shrink-0" />
|
||||||
<p>Estimate</p>
|
<p>Estimate</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:basis-1/2">
|
<div className="sm:basis-1/2">
|
||||||
<IssueEstimateSelect chevron={true} value={value} onChange={onChange} />
|
<CustomSelect
|
||||||
|
value={value}
|
||||||
|
label={
|
||||||
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<PlayIcon className="h-4 w-4 text-gray-700 -rotate-90" />
|
||||||
|
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
||||||
|
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onChange={onChange}
|
||||||
|
position="right"
|
||||||
|
width="w-full"
|
||||||
|
disabled={isNotAllowed}
|
||||||
|
>
|
||||||
|
{estimatePoints &&
|
||||||
|
estimatePoints.map((point) => (
|
||||||
|
<CustomSelect.Option className="w-full " key={point.key} value={point.key}>
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
</span>
|
||||||
|
{point.value}
|
||||||
|
</>
|
||||||
|
</CustomSelect.Option>
|
||||||
|
))}
|
||||||
|
</CustomSelect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import { PRIORITIES } from "constants/project";
|
|||||||
// services
|
// services
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track-event.service";
|
||||||
import useEstimateOption from "hooks/use-estimate-option";
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
|
import { PlayIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
@ -27,15 +28,17 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
selfPositioned = false,
|
selfPositioned = false,
|
||||||
isNotAllowed,
|
isNotAllowed,
|
||||||
}) => {
|
}) => {
|
||||||
const { isEstimateActive, estimatePoints, estimateValue } = useEstimateOption(
|
const { isEstimateActive, estimatePoints } = useEstimateOption(issue.estimate_point);
|
||||||
issue.estimate_point
|
|
||||||
);
|
const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value;
|
||||||
|
|
||||||
|
if (!isEstimateActive) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
value={issue.priority}
|
value={issue.estimate_point}
|
||||||
onChange={(data: string) => {
|
onChange={(val: number) => {
|
||||||
partialUpdateIssue({ priority: data, state: issue.state, target_date: issue.target_date });
|
partialUpdateIssue({ estimate_point: val });
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
workspaceSlug: issue.workspace_detail.slug,
|
||||||
@ -45,12 +48,15 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
projectName: issue.project_detail.name,
|
projectName: issue.project_detail.name,
|
||||||
issueId: issue.id,
|
issueId: issue.id,
|
||||||
},
|
},
|
||||||
"ISSUE_PROPERTY_UPDATE_PRIORITY"
|
"ISSUE_PROPERTY_UPDATE_ESTIMATE"
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
label={
|
label={
|
||||||
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue}>
|
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue}>
|
||||||
<>{estimateValue}</>
|
<div className="flex items-center gap-1 text-gray-500">
|
||||||
|
<PlayIcon className="h-3.5 w-3.5 -rotate-90" />
|
||||||
|
{estimateValue}
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
maxHeight="md"
|
maxHeight="md"
|
||||||
@ -58,6 +64,7 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
position={position}
|
position={position}
|
||||||
selfPositioned={selfPositioned}
|
selfPositioned={selfPositioned}
|
||||||
|
width="w-full min-w-[6rem]"
|
||||||
>
|
>
|
||||||
{estimatePoints?.map((estimate) => (
|
{estimatePoints?.map((estimate) => (
|
||||||
<CustomSelect.Option key={estimate.id} value={estimate.key} className="capitalize">
|
<CustomSelect.Option key={estimate.id} value={estimate.key} className="capitalize">
|
||||||
|
@ -29,7 +29,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
<CustomSelect
|
<CustomSelect
|
||||||
value={issue.priority}
|
value={issue.priority}
|
||||||
onChange={(data: string) => {
|
onChange={(data: string) => {
|
||||||
partialUpdateIssue({ priority: data, state: issue.state, target_date: issue.target_date });
|
partialUpdateIssue({ priority: data });
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
workspaceSlug: issue.workspace_detail.slug,
|
||||||
|
@ -8,6 +8,8 @@ import useSWR from "swr";
|
|||||||
import estimatesService from "services/estimates.service";
|
import estimatesService from "services/estimates.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
|
// helpers
|
||||||
|
import { orderArrayBy } from "helpers/array.helper";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ const useEstimateOption = (estimateKey?: number) => {
|
|||||||
estimatesService.getEstimatesPointsList(
|
estimatesService.getEstimatesPointsList(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
projectId as string,
|
projectId as string,
|
||||||
projectDetails.estimate
|
projectDetails.estimate as string
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
@ -41,7 +43,7 @@ const useEstimateOption = (estimateKey?: number) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isEstimateActive: projectDetails?.estimate ? true : false,
|
isEstimateActive: projectDetails?.estimate ? true : false,
|
||||||
estimatePoints,
|
estimatePoints: orderArrayBy(estimatePoints ?? [], "key"),
|
||||||
estimateValue,
|
estimateValue,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ import Container from "layouts/container";
|
|||||||
import AppHeader from "layouts/app-layout/app-header";
|
import AppHeader from "layouts/app-layout/app-header";
|
||||||
import AppSidebar from "layouts/app-layout/app-sidebar";
|
import AppSidebar from "layouts/app-layout/app-sidebar";
|
||||||
import SettingsNavbar from "layouts/settings-navbar";
|
import SettingsNavbar from "layouts/settings-navbar";
|
||||||
|
import { WorkspaceAuthorizationLayout } from "./workspace-authorization-wrapper";
|
||||||
// components
|
// components
|
||||||
import { NotAuthorizedView, JoinProject } from "components/auth-screens";
|
import { NotAuthorizedView, JoinProject } from "components/auth-screens";
|
||||||
import { CommandPalette } from "components/command-palette";
|
import { CommandPalette } from "components/command-palette";
|
||||||
@ -89,6 +90,22 @@ const ProjectAuthorizationWrapped: React.FC<Props> = ({
|
|||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : error?.status === 401 || error?.status === 403 ? (
|
||||||
|
<JoinProject />
|
||||||
|
) : error?.status === 404 ? (
|
||||||
|
<div className="container h-screen grid place-items-center">
|
||||||
|
<div className="text-center space-y-4">
|
||||||
|
<p className="text-2xl font-semibold">No such project exist. Create one?</p>
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "p" });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create project
|
||||||
|
</PrimaryButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : settingsLayout && (memberType?.isGuest || memberType?.isViewer) ? (
|
) : settingsLayout && (memberType?.isGuest || memberType?.isViewer) ? (
|
||||||
<NotAuthorizedView
|
<NotAuthorizedView
|
||||||
actionButton={
|
actionButton={
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
@ -15,20 +15,20 @@ import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates"
|
|||||||
//hooks
|
//hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader, SecondaryButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IEstimate } from "types";
|
import { IEstimate, IProject } from "types";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATES_LIST } from "constants/fetch-keys";
|
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||||
|
import projectService from "services/project.service";
|
||||||
|
|
||||||
const EstimatesSettings: NextPage = () => {
|
const EstimatesSettings: NextPage = () => {
|
||||||
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
|
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
|
||||||
|
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
|
||||||
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
|
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -38,8 +38,6 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
const scrollToRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const { data: estimatesList } = useSWR<IEstimate[]>(
|
const { data: estimatesList } = useSWR<IEstimate[]>(
|
||||||
workspaceSlug && projectId ? ESTIMATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? ESTIMATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
@ -48,7 +46,6 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const editEstimate = (estimate: IEstimate) => {
|
const editEstimate = (estimate: IEstimate) => {
|
||||||
setIsUpdating(true);
|
|
||||||
setEstimateToUpdate(estimate);
|
setEstimateToUpdate(estimate);
|
||||||
setEstimateFormOpen(true);
|
setEstimateFormOpen(true);
|
||||||
};
|
};
|
||||||
@ -73,6 +70,30 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disableEstimates = () => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
mutate<IProject>(
|
||||||
|
PROJECT_DETAILS(projectId as string),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
|
return { ...prevData, estimate: null };
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
projectService
|
||||||
|
.updateProject(workspaceSlug as string, projectId as string, { estimate: null })
|
||||||
|
.catch(() =>
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Estimate could not be disabled. Please try again",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProjectAuthorizationWrapper
|
<ProjectAuthorizationWrapper
|
||||||
@ -87,7 +108,6 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CreateUpdateEstimateModal
|
<CreateUpdateEstimateModal
|
||||||
isCreate={estimateToUpdate ? true : false}
|
|
||||||
isOpen={estimateFormOpen}
|
isOpen={estimateFormOpen}
|
||||||
data={estimateToUpdate}
|
data={estimateToUpdate}
|
||||||
handleClose={() => {
|
handleClose={() => {
|
||||||
@ -95,14 +115,12 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
setEstimateToUpdate(undefined);
|
setEstimateToUpdate(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<section className="grid grid-cols-12 gap-10">
|
<section className="flex items-center justify-between">
|
||||||
<div className="col-span-12 sm:col-span-5">
|
<h3 className="text-2xl font-semibold">Estimates</h3>
|
||||||
<h3 className="text-[28px] font-semibold">Estimates</h3>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-12 space-y-5 sm:col-span-7">
|
<div className="col-span-12 space-y-5 sm:col-span-7">
|
||||||
<div className="flex sm:justify-end sm:items-end sm:h-full text-theme">
|
<div className="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className="flex items-center cursor-pointer gap-2"
|
className="flex items-center cursor-pointer gap-2 text-theme"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEstimateToUpdate(undefined);
|
setEstimateToUpdate(undefined);
|
||||||
setEstimateFormOpen(true);
|
setEstimateFormOpen(true);
|
||||||
@ -111,6 +129,9 @@ const EstimatesSettings: NextPage = () => {
|
|||||||
<PlusIcon className="h-4 w-4" />
|
<PlusIcon className="h-4 w-4" />
|
||||||
Create New Estimate
|
Create New Estimate
|
||||||
</span>
|
</span>
|
||||||
|
{projectDetails?.estimate && (
|
||||||
|
<SecondaryButton onClick={disableEstimates}>Disable Estimates</SecondaryButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -22,9 +22,9 @@ import { ONBOARDING_CARDS } from "constants/workspace";
|
|||||||
import Logo from "public/onboarding/logo.svg";
|
import Logo from "public/onboarding/logo.svg";
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
import { ICurrentUserResponse } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CURRENT_USER } from "constants/fetch-keys";
|
import { CURRENT_USER } from "constants/fetch-keys";
|
||||||
import { ICurrentUserResponse } from "types";
|
|
||||||
|
|
||||||
const Onboarding: NextPage = () => {
|
const Onboarding: NextPage = () => {
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// services
|
// services
|
||||||
import APIService from "services/api.service";
|
import APIService from "services/api.service";
|
||||||
|
// types
|
||||||
import type { IEstimate, IEstimatePoint } from "types";
|
import type { IEstimate, IEstimatePoint } from "types";
|
||||||
|
|
||||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||||
@ -78,7 +78,7 @@ class ProjectEstimateServices extends APIService {
|
|||||||
}
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.post(
|
return this.post(
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/bulk-create-estimate-points/`,
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/bulk-estimate-points/`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
@ -87,7 +87,7 @@ class ProjectEstimateServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEstimatesPoints(
|
async getEstimatesPointDetails(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
estimateId: string,
|
estimateId: string,
|
||||||
@ -116,15 +116,14 @@ class ProjectEstimateServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEstimatesPoints(
|
async patchEstimatePoints(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
estimateId: string,
|
estimateId: string,
|
||||||
estimatePointId: string,
|
|
||||||
data: any
|
data: any
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.patch(
|
return this.patch(
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimate/${estimateId}/estimate-points/${estimatePointId}`,
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/bulk-estimate-points/`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
@ -132,21 +131,6 @@ class ProjectEstimateServices extends APIService {
|
|||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteEstimatesPoints(
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
estimateId: string,
|
|
||||||
estimatePointId: string
|
|
||||||
): Promise<any> {
|
|
||||||
return this.delete(
|
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimate/${estimateId}/estimate-points/${estimatePointId}`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
|
||||||
.catch((error) => {
|
|
||||||
throw error?.response?.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ProjectEstimateServices();
|
export default new ProjectEstimateServices();
|
||||||
|
@ -192,6 +192,7 @@ class TrackEventServices extends APIService {
|
|||||||
| "ISSUE_PROPERTY_UPDATE_STATE"
|
| "ISSUE_PROPERTY_UPDATE_STATE"
|
||||||
| "ISSUE_PROPERTY_UPDATE_ASSIGNEE"
|
| "ISSUE_PROPERTY_UPDATE_ASSIGNEE"
|
||||||
| "ISSUE_PROPERTY_UPDATE_DUE_DATE"
|
| "ISSUE_PROPERTY_UPDATE_DUE_DATE"
|
||||||
|
| "ISSUE_PROPERTY_UPDATE_ESTIMATE"
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (!trackEvent) return;
|
if (!trackEvent) return;
|
||||||
return this.request({
|
return this.request({
|
||||||
|
2
apps/app/types/projects.d.ts
vendored
2
apps/app/types/projects.d.ts
vendored
@ -18,7 +18,7 @@ export interface IProject {
|
|||||||
page_view: boolean;
|
page_view: boolean;
|
||||||
default_assignee: IUser | string | null;
|
default_assignee: IUser | string | null;
|
||||||
description: string;
|
description: string;
|
||||||
estimate: string;
|
estimate: string | null;
|
||||||
icon: string;
|
icon: string;
|
||||||
id: string;
|
id: string;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user