chore: updated create estimate and handled the build error

This commit is contained in:
guru_sainath 2024-05-24 15:07:44 +05:30
parent bc1702647c
commit 3800f4366c
45 changed files with 813 additions and 1756 deletions

View File

@ -31,13 +31,11 @@ export interface IEstimate {
}
export interface IEstimateFormData {
estimate: {
name: string;
description: string;
estimate?: {
type: string;
};
estimate_points: {
id?: string;
id?: string | undefined;
key: number;
value: string;
}[];

View File

@ -29,7 +29,7 @@
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
"react-popper": "^2.3.0",
"sonner": "^1.4.2",
"sonner": "^1.4.41",
"tailwind-merge": "^2.0.0"
},
"devDependencies": {

View File

@ -14,7 +14,7 @@ type RadioInputProps = {
className?: string;
};
const RadioInput = ({
export const RadioInput = ({
label: inputLabel,
labelClassName: inputLabelClassName,
options,
@ -63,4 +63,4 @@ const RadioInput = ({
);
};
export { RadioInput };
export default RadioInput;

View File

@ -21,7 +21,7 @@ const moveItems = <T,>(data: T[], source: T, destination: T): T[] => {
return newData;
};
const Sortable = <T,>({ data, render, onChange, keyExtractor }: Props<T>) => {
export const Sortable = <T,>({ data, render, onChange, keyExtractor }: Props<T>) => {
useEffect(() => {
const unsubscribe = monitorForElements({
onDrop({ source, location }) {
@ -46,4 +46,4 @@ const Sortable = <T,>({ data, render, onChange, keyExtractor }: Props<T>) => {
);
};
export { Sortable };
export default Sortable;

View File

@ -24,7 +24,10 @@ import { Tooltip, BlockedIcon, BlockerIcon, RelatedIcon, LayersIcon, DiceIcon }
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { capitalizeFirstLetter } from "@/helpers/string.helper";
import { useEstimate, useLabel } from "@/hooks/store";
import {
// useEstimate,
useLabel,
} from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// types
@ -97,21 +100,21 @@ const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; works
);
});
const EstimatePoint = observer((props: { point: string }) => {
const { point } = props;
const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
const currentPoint = Number(point) + 1;
// const EstimatePoint = observer((props: { point: string }) => {
// const { point } = props;
// const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
// const currentPoint = Number(point) + 1;
const estimateValue = getEstimatePointValue(Number(point), null);
// const estimateValue = getEstimatePointValue(Number(point), null);
return (
<span className="font-medium text-custom-text-100 whitespace-nowrap">
{areEstimatesEnabledForCurrentProject
? estimateValue
: `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`}
</span>
);
});
// return (
// <span className="font-medium text-custom-text-100 whitespace-nowrap">
// {areEstimatesEnabledForCurrentProject
// ? estimateValue
// : `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`}
// </span>
// );
// });
const inboxActivityMessage = {
declined: {
@ -267,7 +270,8 @@ const activityDetails: {
else
return (
<>
set the estimate point to <EstimatePoint point={activity.new_value} />
set the estimate point to
{/* <EstimatePoint point={activity.new_value} /> */}
{showIssue && (
<>
{" "}

View File

@ -1,11 +1,11 @@
import React, { useEffect, useState, useRef, Fragment } from "react";
import React, { useEffect, useState, useRef, Fragment, Ref } from "react";
import { Placement } from "@popperjs/core";
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form"; // services
import { usePopper } from "react-popper";
// ui
import { AlertCircle } from "lucide-react";
import { Popover, Transition } from "@headlessui/react";
import { Popover, PopoverButton, PopoverPanel, Transition } from "@headlessui/react";
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/rich-text-read-only-editor";
// icons
@ -178,9 +178,9 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
return (
<Popover as="div" className={`relative w-min text-left`}>
<Popover.Button as={Fragment}>
<PopoverButton as={Fragment}>
<button ref={setReferenceElement}>{button}</button>
</Popover.Button>
</PopoverButton>
<Transition
show={isOpen}
as={React.Fragment}
@ -191,10 +191,10 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Popover.Panel
<PopoverPanel
as="div"
className={`fixed z-10 flex w-full min-w-[50rem] max-w-full flex-col space-y-4 overflow-hidden rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow ${className}`}
ref={setPopperElement}
ref={setPopperElement as Ref<HTMLDivElement>}
style={styles.popper}
{...attributes.popper}
>
@ -261,7 +261,7 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
</Button>
</div>
</div>
</Popover.Panel>
</PopoverPanel>
</Transition>
</Popover>
);

View File

@ -7,7 +7,10 @@ import { Combobox } from "@headlessui/react";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppRouter, useEstimate } from "@/hooks/store";
import {
useAppRouter,
// useEstimate
} from "@/hooks/store";
import { useDropdown } from "@/hooks/use-dropdown";
// components
import { DropdownButton } from "./buttons";
@ -76,8 +79,12 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
});
// store hooks
const { workspaceSlug } = useAppRouter();
const { fetchProjectEstimates, getProjectActiveEstimateDetails, getEstimatePointValue } = useEstimate();
const activeEstimate = getProjectActiveEstimateDetails(projectId);
console.log("workspaceSlug", workspaceSlug);
console.log("projectId", projectId);
// const { fetchProjectEstimates, getProjectActiveEstimateDetails, getEstimatePointValue } = useEstimate();
// const activeEstimate = getProjectActiveEstimateDetails(projectId);
const activeEstimate: any = undefined;
const options: DropdownOptions = sortBy(activeEstimate?.points ?? [], "key")?.map((point) => ({
value: point.key,
@ -103,10 +110,14 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
const filteredOptions =
query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
const selectedEstimate = value !== null ? getEstimatePointValue(value, projectId) : null;
const selectedEstimate =
value !== null
? // getEstimatePointValue(value, projectId)
null
: null;
const onOpen = async () => {
if (!activeEstimate && workspaceSlug) await fetchProjectEstimates(workspaceSlug, projectId);
// if (!activeEstimate && workspaceSlug) await fetchProjectEstimates(workspaceSlug, projectId);
};
const { handleClose, handleKeyDown, handleOnClick, searchInputKeyDown } = useDropdown({

View File

@ -1,6 +1,6 @@
import { useState } from "react";
import { Ref, useState } from "react";
import { usePopper } from "react-popper";
import { Popover } from "@headlessui/react";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
// popper
// helper
import { getButtonStyling } from "@plane/ui";
@ -42,7 +42,7 @@ export const ComicBoxButton: React.FC<Props> = (props) => {
return (
<Popover>
<Popover.Button ref={setReferenceElement} onClick={onClick} disabled={disabled}>
<PopoverButton ref={setReferenceElement} onClick={onClick} disabled={disabled}>
<div className={`flex items-center gap-2.5 ${getButtonStyling("primary", "lg", disabled)}`}>
{icon}
<span className="leading-4">{label}</span>
@ -55,12 +55,12 @@ export const ComicBoxButton: React.FC<Props> = (props) => {
<div className={`absolute bg-blue-400/40 right-0 h-1.5 w-1.5 mt-0.5 mr-0.5 rounded-full`} />
</span>
</div>
</Popover.Button>
</PopoverButton>
{isHovered && (
<Popover.Panel
<PopoverPanel
as="div"
className="flex flex-col rounded border border-custom-border-200 bg-custom-background-100 p-5 relative min-w-80"
ref={setPopperElement}
ref={setPopperElement as Ref<HTMLDivElement>}
style={styles.popper}
{...attributes.popper}
static
@ -68,7 +68,7 @@ export const ComicBoxButton: React.FC<Props> = (props) => {
<div className="absolute w-2 h-2 bg-custom-background-100 border rounded-lb-sm border-custom-border-200 border-r-0 border-t-0 transform rotate-45 bottom-2 -left-[5px]" />
<h3 className="text-lg font-semibold w-full">{title}</h3>
<h4 className="mt-1 text-sm">{description}</h4>
</Popover.Panel>
</PopoverPanel>
)}
</Popover>
);

View File

@ -1,292 +0,0 @@
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form";
// types
import { IEstimate, IEstimateFormData } from "@plane/types";
// ui
import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// helpers
import { checkDuplicates } from "@/helpers/array.helper";
// hooks
import { useEstimate } from "@/hooks/store";
type Props = {
isOpen: boolean;
handleClose: () => void;
data?: IEstimate;
};
const defaultValues = {
name: "",
description: "",
value1: "",
value2: "",
value3: "",
value4: "",
value5: "",
value6: "",
};
type FormValues = typeof defaultValues;
export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
const { handleClose, data, isOpen } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { createEstimate, updateEstimate } = useEstimate();
// form info
const {
formState: { errors, isSubmitting },
handleSubmit,
control,
reset,
} = useForm<FormValues>({
defaultValues,
});
const onClose = () => {
handleClose();
reset();
};
const handleCreateEstimate = async (payload: IEstimateFormData) => {
if (!workspaceSlug || !projectId) return;
await createEstimate(workspaceSlug.toString(), projectId.toString(), payload)
.then(() => {
onClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message:
errorString ?? err.status === 400
? "Estimate with that name already exists. Please try again with another name."
: "Estimate could not be created. Please try again.",
});
});
};
const handleUpdateEstimate = async (payload: IEstimateFormData) => {
if (!workspaceSlug || !projectId || !data) return;
await updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload)
.then(() => {
onClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be updated. Please try again.",
});
});
};
const onSubmit = async (formData: FormValues) => {
if (!formData.name || formData.name === "") {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate title cannot be empty.",
});
return;
}
if (
formData.value1 === "" ||
formData.value2 === "" ||
formData.value3 === "" ||
formData.value4 === "" ||
formData.value5 === "" ||
formData.value6 === ""
) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate point cannot be empty.",
});
return;
}
if (
formData.value1.length > 20 ||
formData.value2.length > 20 ||
formData.value3.length > 20 ||
formData.value4.length > 20 ||
formData.value5.length > 20 ||
formData.value6.length > 20
) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate point cannot have more than 20 characters.",
});
return;
}
if (
checkDuplicates([
formData.value1,
formData.value2,
formData.value3,
formData.value4,
formData.value5,
formData.value6,
])
) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate points cannot have duplicate values.",
});
return;
}
const payload: IEstimateFormData = {
estimate: {
name: formData.name,
description: formData.description,
},
estimate_points: [],
};
for (let i = 0; i < 6; i++) {
const point = {
key: i,
value: formData[`value${i + 1}` as keyof FormValues],
};
if (data)
payload.estimate_points.push({
id: data.points[i].id,
...point,
});
else payload.estimate_points.push({ ...point });
}
if (data) await handleUpdateEstimate(payload);
else await handleCreateEstimate(payload);
};
useEffect(() => {
if (data)
reset({
...defaultValues,
...data,
value1: data.points[0]?.value,
value2: data.points[1]?.value,
value3: data.points[2]?.value,
value4: data.points[3]?.value,
value5: data.points[4]?.value,
value6: data.points[5]?.value,
});
else reset({ ...defaultValues });
}, [data, reset]);
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-5 p-5">
<div className="text-xl font-medium text-custom-text-200">{data ? "Update" : "Create"} Estimate</div>
<div className="space-y-3">
<div>
<Controller
control={control}
name="name"
render={({ field: { value, onChange, ref } }) => (
<Input
id="name"
name="name"
type="name"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.name)}
placeholder="Title"
className="w-full text-base"
/>
)}
/>
</div>
<div>
<Controller
name="description"
control={control}
render={({ field: { value, onChange } }) => (
<TextArea
id="description"
name="description"
value={value}
placeholder="Description"
onChange={onChange}
className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)}
/>
)}
/>
</div>
</div>
{/* list of all the points */}
{/* since they are all the same, we can use a loop to render them */}
<div className="grid grid-cols-3 gap-3">
{Array(6)
.fill(0)
.map((_, i) => (
<div className="flex items-center" key={i}>
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">{i + 1}</span>
<span className="rounded-r-lg bg-custom-background-100">
<Controller
control={control}
name={`value${i + 1}` as keyof FormValues}
rules={{
maxLength: {
value: 20,
message: "Estimate point must at most be of 20 characters",
},
}}
render={({ field: { value, onChange, ref } }) => (
<Input
ref={ref}
type="text"
value={value}
onChange={onChange}
id={`value${i + 1}`}
name={`value${i + 1}`}
placeholder={`Point ${i + 1}`}
className="w-full rounded-l-none"
hasError={Boolean(errors[`value${i + 1}` as keyof FormValues])}
/>
)}
/>
</span>
</span>
</div>
))}
</div>
</div>
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{data ? (isSubmitting ? "Updating" : "Update Estimate") : isSubmitting ? "Creating" : "Create Estimate"}
</Button>
</div>
</form>
</ModalCore>
);
});

View File

@ -1,79 +0,0 @@
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types
import { IEstimate } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// hooks
import { useEstimate } from "@/hooks/store";
type Props = {
isOpen: boolean;
data: IEstimate | null;
handleClose: () => void;
};
export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, data } = props;
// states
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { deleteEstimate } = useEstimate();
const handleEstimateDelete = async () => {
if (!workspaceSlug || !projectId) return;
setIsDeleteLoading(true);
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const estimateId = data?.id!;
await deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId)
.then(() => {
handleClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be deleted. Please try again",
});
})
.finally(() => setIsDeleteLoading(false));
};
useEffect(() => {
setIsDeleteLoading(false);
}, [isOpen]);
const onClose = () => {
setIsDeleteLoading(false);
handleClose();
};
return (
<AlertModalCore
handleClose={onClose}
handleSubmit={handleEstimateDelete}
isDeleting={isDeleteLoading}
isOpen={isOpen}
title="Delete Estimate"
content={
<>
Are you sure you want to delete estimate-{" "}
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>
{""}? All of the data related to the estiamte will be permanently removed. This action cannot be undone.
</>
}
/>
);
});

View File

@ -1,114 +0,0 @@
import React from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// hooks
import { Pencil, Trash2 } from "lucide-react";
import { IEstimate } from "@plane/types";
import { Button, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
import { orderArrayBy } from "@/helpers/array.helper";
import { useProject } from "@/hooks/store";
// ui
//icons
// helpers
// types
type Props = {
estimate: IEstimate;
editEstimate: (estimate: IEstimate) => void;
deleteEstimate: (estimateId: string) => void;
};
export const EstimateListItem: React.FC<Props> = observer((props) => {
const { estimate, editEstimate, deleteEstimate } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { currentProjectDetails, updateProject } = useProject();
const handleUseEstimate = async () => {
if (!workspaceSlug || !projectId) return;
await updateProject(workspaceSlug.toString(), projectId.toString(), {
estimate: estimate.id,
}).catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate points could not be used. Please try again.",
});
});
};
return (
<>
<div className="gap-2 border-b border-custom-border-100 p-4">
<div className="flex items-center justify-between">
<div>
<h6 className="flex w-[40vw] items-center gap-2 truncate text-sm font-medium">
{estimate.name}
{currentProjectDetails?.estimate && currentProjectDetails?.estimate === estimate.id && (
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs text-green-500">In use</span>
)}
</h6>
<p className="font-sm w-[40vw] truncate text-[14px] font-normal text-custom-text-200">
{estimate.description}
</p>
</div>
<div className="flex items-center gap-2">
{currentProjectDetails?.estimate !== estimate?.id && estimate?.points?.length > 0 && (
<Button variant="neutral-primary" onClick={handleUseEstimate} size="sm">
Use
</Button>
)}
<CustomMenu ellipsis>
<CustomMenu.MenuItem
onClick={() => {
editEstimate(estimate);
}}
>
<div className="flex items-center justify-start gap-2">
<Pencil className="h-3.5 w-3.5" />
<span>Edit estimate</span>
</div>
</CustomMenu.MenuItem>
{currentProjectDetails?.estimate !== estimate.id && (
<CustomMenu.MenuItem
onClick={() => {
deleteEstimate(estimate.id);
}}
>
<div className="flex items-center justify-start gap-2">
<Trash2 className="h-3.5 w-3.5" />
<span>Delete estimate</span>
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
</div>
</div>
{estimate?.points?.length > 0 ? (
<div className="flex text-xs text-custom-text-200">
Estimate points (
<span className="flex gap-1">
{orderArrayBy(estimate.points, "key").map((point, index) => (
<h6 key={point.id} className="text-custom-text-200">
{point.value}
{index !== estimate.points.length - 1 && ","}{" "}
</h6>
))}
</span>
)
</div>
) : (
<div>
<p className="text-xs text-custom-text-200">No estimate points</p>
</div>
)}
</div>
</>
);
});

View File

@ -1,121 +0,0 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { IEstimate } from "@plane/types";
// store hooks
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
import { EmptyState } from "@/components/empty-state";
import { CreateUpdateEstimateModal, DeleteEstimateModal, EstimateListItem } from "@/components/estimates-legacy";
import { EmptyStateType } from "@/constants/empty-state";
import { orderArrayBy } from "@/helpers/array.helper";
import { useEstimate, useProject } from "@/hooks/store";
// components
// ui
// types
// helpers
// constants
export const EstimatesList: React.FC = observer(() => {
// states
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
const [estimateToDelete, setEstimateToDelete] = useState<string | null>(null);
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { updateProject, currentProjectDetails } = useProject();
const { projectEstimates, getProjectEstimateById } = useEstimate();
const editEstimate = (estimate: IEstimate) => {
setEstimateFormOpen(true);
// Order the points array by key before updating the estimate to update state
setEstimateToUpdate({
...estimate,
points: orderArrayBy(estimate.points, "key"),
});
};
const disableEstimates = () => {
if (!workspaceSlug || !projectId) return;
updateProject(workspaceSlug.toString(), projectId.toString(), { estimate: null }).catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be disabled. Please try again",
});
});
};
return (
<>
<CreateUpdateEstimateModal
isOpen={estimateFormOpen}
data={estimateToUpdate}
handleClose={() => {
setEstimateFormOpen(false);
setEstimateToUpdate(undefined);
}}
/>
<DeleteEstimateModal
isOpen={!!estimateToDelete}
handleClose={() => setEstimateToDelete(null)}
data={getProjectEstimateById(estimateToDelete!)}
/>
<section className="flex items-center justify-between border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Estimates</h3>
<div className="col-span-12 space-y-5 sm:col-span-7">
<div className="flex items-center gap-2">
<Button
variant="primary"
onClick={() => {
setEstimateFormOpen(true);
setEstimateToUpdate(undefined);
}}
size="sm"
>
Add Estimate
</Button>
{currentProjectDetails?.estimate && (
<Button variant="neutral-primary" onClick={disableEstimates} size="sm">
Disable Estimates
</Button>
)}
</div>
</div>
</section>
{projectEstimates ? (
projectEstimates.length > 0 ? (
<section className="h-full overflow-y-auto bg-custom-background-100">
{projectEstimates.map((estimate) => (
<EstimateListItem
key={estimate.id}
estimate={estimate}
editEstimate={(estimate) => editEstimate(estimate)}
deleteEstimate={(estimateId) => setEstimateToDelete(estimateId)}
/>
))}
</section>
) : (
<div className="h-full w-full py-8">
<EmptyState type={EmptyStateType.PROJECT_SETTINGS_ESTIMATE} />
</div>
)
) : (
<Loader className="mt-5 space-y-5">
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
</Loader>
)}
</>
);
});

View File

@ -1,4 +0,0 @@
export * from "./create-update-estimate-modal";
export * from "./delete-estimate-modal";
export * from "./estimate-list-item";
export * from "./estimates-list";

View File

@ -2,32 +2,35 @@ import { FC, useEffect, useMemo, useState } from "react";
import cloneDeep from "lodash/cloneDeep";
import { observer } from "mobx-react";
import { ChevronLeft } from "lucide-react";
import { IEstimate } from "@plane/types";
import { Button } from "@plane/ui";
import { IEstimate, IEstimateFormData } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { EstimateCreateStageOne, EstimateCreateStageTwo } from "@/components/estimates";
import { TEstimateSystemKeys, EEstimateSystem, TEstimateSystemKeyObject } from "@/components/estimates/types";
import { TEstimateSystemKeys, EEstimateSystem, TEstimatePointsObject } from "@/components/estimates/types";
// constants
import { ESTIMATE_SYSTEMS } from "@/constants/estimates";
// ee components
// hooks
import { useProjectEstimates } from "@/hooks/store";
type Props = {
type TCreateEstimateModal = {
workspaceSlug: string;
projectId: string;
isOpen: boolean;
handleClose: () => void;
data?: IEstimate;
};
export const CreateEstimateModal: FC<Props> = observer((props) => {
export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) => {
// props
const { handleClose, isOpen } = props;
const { workspaceSlug, projectId, isOpen, handleClose } = props;
// hooks
const { createEstimate } = useProjectEstimates();
// states
const [estimateSystem, setEstimateSystem] = useState<TEstimateSystemKeys>(EEstimateSystem.POINTS);
const [estimatePoints, setEstimatePoints] = useState<TEstimateSystemKeyObject[TEstimateSystemKeys] | undefined>(
undefined
);
const [estimatePoints, setEstimatePoints] = useState<TEstimatePointsObject[] | undefined>(undefined);
const handleUpdatePoints = (newPoints: TEstimateSystemKeyObject[TEstimateSystemKeys] | undefined) => {
const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => {
const points = cloneDeep(newPoints);
setEstimatePoints(points);
};
@ -42,6 +45,46 @@ export const CreateEstimateModal: FC<Props> = observer((props) => {
// derived values
const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]);
const handleCreateEstimate = async () => {
try {
const validatedEstimatePoints: TEstimatePointsObject[] = [];
if ([EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateSystem)) {
estimatePoints?.map((estimatePoint) => {
if (estimatePoint.value && Number(estimatePoint.value)) validatedEstimatePoints.push(estimatePoint);
});
} else {
estimatePoints?.map((estimatePoint) => {
if (estimatePoint.value) validatedEstimatePoints.push(estimatePoint);
});
}
if (validatedEstimatePoints.length === estimatePoints?.length) {
const payload: IEstimateFormData = {
estimate: {
type: estimateSystem,
},
estimate_points: validatedEstimatePoints,
};
await createEstimate(workspaceSlug, projectId, payload);
handleClose();
} else {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "something went wrong",
});
}
} catch (error) {
console.log(error);
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "something went wrong",
});
}
};
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<div className="relative space-y-6 py-5">
@ -70,7 +113,7 @@ export const CreateEstimateModal: FC<Props> = observer((props) => {
<EstimateCreateStageOne
estimateSystem={estimateSystem}
handleEstimateSystem={setEstimateSystem}
handleEstimatePoints={(templateType) =>
handleEstimatePoints={(templateType: string) =>
handleUpdatePoints(ESTIMATE_SYSTEMS[estimateSystem].templates[templateType].values)
}
/>
@ -89,7 +132,7 @@ export const CreateEstimateModal: FC<Props> = observer((props) => {
Cancel
</Button>
{estimatePoints && (
<Button variant="primary" size="sm" onClick={handleClose}>
<Button variant="primary" size="sm" onClick={handleCreateEstimate}>
Create Estimate
</Button>
)}

View File

@ -9,7 +9,7 @@ import { ESTIMATE_SYSTEMS } from "@/constants/estimates";
type TEstimateCreateStageOne = {
estimateSystem: TEstimateSystemKeys;
handleEstimateSystem: (value: TEstimateSystemKeys) => void;
handleEstimatePoints: (value: TEstimateSystemKeys) => void;
handleEstimatePoints: (value: string) => void;
};
export const EstimateCreateStageOne: FC<TEstimateCreateStageOne> = (props) => {
@ -44,7 +44,7 @@ export const EstimateCreateStageOne: FC<TEstimateCreateStageOne> = (props) => {
<button
key={name}
className="border border-custom-border-200 rounded-md p-2 text-left"
onClick={() => handleEstimatePoints(name as TEstimateSystemKeys)}
onClick={() => handleEstimatePoints(name)}
>
<p className="block text-sm">{currentEstimateSystem.templates[name]?.title}</p>
<p className="text-xs text-gray-400">

View File

@ -3,20 +3,14 @@ import { Plus } from "lucide-react";
import { Button, Sortable } from "@plane/ui";
// components
import { EstimateItem } from "@/components/estimates";
import {
EEstimateSystem,
TEstimatePointNumeric,
TEstimatePointString,
TEstimateSystemKeyObject,
TEstimateSystemKeys,
} from "@/components/estimates/types";
import { EEstimateSystem, TEstimatePointsObject } from "@/components/estimates/types";
// constants
import { ESTIMATE_SYSTEMS } from "@/constants/estimates";
type TEstimateCreateStageTwo = {
estimateSystem: EEstimateSystem;
estimatePoints: TEstimateSystemKeyObject[TEstimateSystemKeys];
handleEstimatePoints: (value: TEstimateSystemKeyObject[TEstimateSystemKeys]) => void;
estimatePoints: TEstimatePointsObject[];
handleEstimatePoints: (value: TEstimatePointsObject[]) => void;
};
export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
@ -26,20 +20,12 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
const addNewEstimationPoint = () => {
const currentEstimationPoints = estimatePoints;
if ([EEstimateSystem.POINTS, EEstimateSystem.TIME].includes(estimateSystem)) {
const newEstimationPoint: TEstimatePointNumeric = {
const newEstimationPoint: TEstimatePointsObject = {
key: currentEstimationPoints.length + 1,
value: 0,
value: "0",
};
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint] as TEstimatePointNumeric[]);
}
if (estimateSystem === EEstimateSystem.CATEGORIES) {
const newEstimationPoint: TEstimatePointString = {
key: currentEstimationPoints.length + 1,
value: "",
};
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint] as TEstimatePointString[]);
}
handleEstimatePoints([...currentEstimationPoints, newEstimationPoint]);
};
const deleteEstimationPoint = (index: number) => {
@ -48,26 +34,21 @@ export const EstimateCreateStageTwo: FC<TEstimateCreateStageTwo> = (props) => {
handleEstimatePoints(newEstimationPoints);
};
const updatedSortedKeys = (updatedEstimatePoints: TEstimateSystemKeyObject[TEstimateSystemKeys]) =>
const updatedSortedKeys = (updatedEstimatePoints: TEstimatePointsObject[]) =>
updatedEstimatePoints.map((item, index) => ({
...item,
key: index + 1,
})) as TEstimateSystemKeyObject[TEstimateSystemKeys];
})) as TEstimatePointsObject[];
return (
<div className="space-y-4">
<Sortable
data={estimatePoints as any}
render={(value: TEstimatePointString | TEstimatePointNumeric, index: number) => (
data={estimatePoints}
render={(value: TEstimatePointsObject, index: number) => (
<EstimateItem item={value} deleteItem={() => deleteEstimationPoint(index)} />
)}
onChange={(data: TEstimateSystemKeyObject[TEstimateSystemKeys]) => {
console.log("updatedSortedKeys(data)", updatedSortedKeys(data));
handleEstimatePoints(updatedSortedKeys(data));
}}
keyExtractor={(item: TEstimatePointString | TEstimatePointNumeric, index: number) =>
item?.id?.toString() || item.value.toString()
}
onChange={(data: TEstimatePointsObject[]) => handleEstimatePoints(updatedSortedKeys(data))}
keyExtractor={(item: TEstimatePointsObject) => item?.id?.toString() || item.value.toString()}
/>
<Button prependIcon={<Plus />} onClick={addNewEstimationPoint}>

View File

@ -3,10 +3,10 @@ import { Check, GripVertical, MoveRight, Pencil, Trash2, X } from "lucide-react"
import { Select } from "@headlessui/react";
import { Draggable } from "@plane/ui";
import { InlineEdit } from "./inline-editable";
import { TEstimatePointNumeric, TEstimatePointString } from "./types";
import { TEstimatePointsObject } from "./types";
type Props = {
item: TEstimatePointNumeric | TEstimatePointString;
item: TEstimatePointsObject;
deleteItem: () => void;
};
const EstimateItem = ({ item, deleteItem }: Props) => {

View File

@ -3,6 +3,7 @@ export * from "./loader-screen";
export * from "./estimate-search";
export * from "./estimate-disable";
export * from "./inline-editable";
export * from "./root";
export * from "./estimate-item";

View File

@ -7,7 +7,8 @@ type Props = {
inputType?: HTMLInputTypeAttribute;
isEditing?: boolean;
};
const InlineEdit = ({
export const InlineEdit = ({
onSave,
value: defaultValue,
inputType = "text",
@ -62,5 +63,3 @@ const InlineEdit = ({
</div>
);
};
export { InlineEdit };

View File

@ -5,7 +5,6 @@ import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EmptyState } from "@/components/empty-state";
import { EstimateEmptyScreen, EstimateLoaderScreen, CreateEstimateModal } from "@/components/estimates";
import { DeleteEstimateModal, EstimateListItem } from "@/components/estimates-legacy";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// ee components
@ -21,29 +20,26 @@ export const EstimateRoot: FC<TEstimateRoot> = (props) => {
const { workspaceSlug, projectId } = props;
// hooks
const { updateProject, currentProjectDetails } = useProject();
const { loader, projectEstimateIds, estimateById, getAllEstimates } = useProjectEstimates(projectId);
const { loader, projectEstimateIds, estimateById, getProjectEstimates } = useProjectEstimates();
// states
const [isEstimateCreateModalOpen, setIsEstimateCreateModalOpen] = useState(false);
const [isEstimateDeleteModalOpen, setIsEstimateDeleteModalOpen] = useState<string | null>(null);
// const [isEstimateDeleteModalOpen, setIsEstimateDeleteModalOpen] = useState<string | null>(null);
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
console.log("workspaceSlug", workspaceSlug);
console.log("projectId", projectId);
const { isLoading: isSWRLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}` : null,
async () => workspaceSlug && projectId && getAllEstimates()
async () => workspaceSlug && projectId && getProjectEstimates(workspaceSlug, projectId)
);
const editEstimate = (estimate: IEstimate) => {
setIsEstimateCreateModalOpen(true);
console.log("estimate", estimate);
// Order the points array by key before updating the estimate to update state
// setEstimateToUpdate({
// ...estimate,
// points: orderArrayBy(estimate.points, "key"),
// });
};
// const editEstimate = (estimate: IEstimate) => {
// setIsEstimateCreateModalOpen(true);
// console.log("estimate", estimate);
// // Order the points array by key before updating the estimate to update state
// // setEstimateToUpdate({
// // ...estimate,
// // points: orderArrayBy(estimate.points, "key"),
// // });
// };
const disableEstimates = () => {
if (!workspaceSlug || !projectId) return;
@ -64,6 +60,7 @@ export const EstimateRoot: FC<TEstimateRoot> = (props) => {
<div className="container mx-auto">
<EstimateLoaderScreen />
<EstimateEmptyScreen onButtonClick={() => {}} />
{loader === "init-loader" || isSWRLoading ? (
<Loader className="mt-5 space-y-5">
<Loader.Item height="40px" />
@ -109,12 +106,13 @@ export const EstimateRoot: FC<TEstimateRoot> = (props) => {
const estimate = estimateById(estimateId);
if (!estimate) return <></>;
return (
<EstimateListItem
key={estimateId}
estimate={estimate}
editEstimate={(estimate) => editEstimate(estimate)}
deleteEstimate={(estimateId) => setIsEstimateDeleteModalOpen(estimateId)}
/>
<></>
// <EstimateListItem
// key={estimateId}
// estimate={estimate}
// editEstimate={(estimate) => editEstimate(estimate)}
// deleteEstimate={(estimateId) => setIsEstimateDeleteModalOpen(estimateId)}
// />
);
})}
</section>
@ -122,7 +120,10 @@ export const EstimateRoot: FC<TEstimateRoot> = (props) => {
</>
)}
{/* */}
<CreateEstimateModal
workspaceSlug={workspaceSlug}
projectId={projectId}
isOpen={isEstimateCreateModalOpen}
data={estimateToUpdate}
handleClose={() => {
@ -130,14 +131,12 @@ export const EstimateRoot: FC<TEstimateRoot> = (props) => {
setEstimateToUpdate(undefined);
}}
/>
<DeleteEstimateModal
{/* <DeleteEstimateModal
isOpen={!!isEstimateDeleteModalOpen}
handleClose={() => setIsEstimateDeleteModalOpen(null)}
data={
null
// getProjectEstimateById(isEstimateDeleteModalOpen!)
}
/>
data={}
/> */}
</div>
);
};

View File

@ -5,25 +5,19 @@ export enum EEstimateSystem {
}
export type TEstimateSystemKeys = EEstimateSystem.POINTS | EEstimateSystem.CATEGORIES | EEstimateSystem.TIME;
export type TEstimatePointString = { key: number; value: string; id: string };
export type TEstimatePointNumeric = { key: number; value: number; id: string };
export type TEstimateSystemKeyObject = {
points: TEstimatePointNumeric[];
categories: TEstimatePointString[];
time: TEstimatePointNumeric[];
};
export type TEstimatePointsObject = { id?: string | undefined; key: number; value: string };
export type TTemplateValues<T extends TEstimateSystemKeys> = {
export type TTemplateValues = {
title: string;
values: TEstimateSystemKeyObject[T];
values: TEstimatePointsObject[];
};
export type TEstimateSystem<T extends TEstimateSystemKeys> = {
export type TEstimateSystem = {
name: string;
templates: Record<string, TTemplateValues<T>>;
templates: Record<string, TTemplateValues>;
is_available: boolean;
};
export type TEstimateSystems = {
[K in TEstimateSystemKeys]: TEstimateSystem<K>;
[K in TEstimateSystemKeys]: TEstimateSystem;
};

View File

@ -1,207 +0,0 @@
import React, { Fragment, useState } from "react";
import { observer } from "mobx-react";
// types
import { ChevronLeft, Plus } from "lucide-react";
import { IEstimate, IEstimateFormData } from "@plane/types";
// ui
import { SubHeading, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { RadioInput } from "@/components/radio-group";
import { Sortable } from "@/components/sortable/sortable";
import { EstimateItem } from "./estimate-item";
import { useEstimate } from "@/hooks/store";
import { useRouter } from "next/router";
// helpers
// hooks
type Props = {
isOpen: boolean;
handleClose: () => void;
data?: IEstimate;
};
const ESTIMATE_SYSTEMS = {
Points: {
name: "Points",
templates: {
Fibonacci: [1, 2, 3, 5, 8, 13, 21],
Linear: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
Squares: [1, 4, 9, 16, 25, 36],
},
},
Categories: {
name: "Categories",
templates: {
"T-Shirt Sizes": ["XS", "S", "M", "L", "XL", "XXL"],
"Easy to hard": ["Easy", "Medium", "Hard", "Very Hard"],
},
},
Time: {
name: "Time",
templates: { Hours: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] },
},
};
type EstimatePoint = {
id?: string;
key: number;
value: string;
};
export const UpdateEstimateModal: React.FC<Props> = observer((props) => {
const { handleClose, isOpen, data } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { createEstimate, updateEstimate } = useEstimate();
console.log({ data });
const [estimateSystem, setEstimateSystem] = useState("Points");
const [points, setPoints] = useState<EstimatePoint[] | null>(data.points);
const currentEstimateSystem = ESTIMATE_SYSTEMS[estimateSystem];
const deleteItem = (index: number) => {
points.splice(index, 1);
setPoints([...points]);
};
const saveEstimate = async () => {
if (!workspaceSlug || !projectId || !data) return;
console.log({ points });
const payload: IEstimateFormData = {
estimate_points: points?.map((point, index) => {
point.key = index;
return point;
}),
estimate: {
name: data.name,
description: data.description,
},
};
console.log({ payload });
await updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload)
.then(() => {
// onClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be updated. Please try again.",
});
});
};
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<div className="p-5">
{!points && (
<Fragment>
<div className="flex justify-between items-center mb-6">
<div className="flex justify-start align-middle items-center ">
<SubHeading noMargin>New Estimate System</SubHeading>
</div>
<div>
<span className="text-xs text-gray-400">Step 2/2</span>
</div>
</div>
<form onSubmit={() => console.log("Submitted")}>
<div className="space-y-4 sm:flex sm:items-center sm:space-x-10 sm:space-y-0 gap-2 mb-2">
<RadioInput
options={Object.keys(ESTIMATE_SYSTEMS).map((name) => ({ label: name, value: name }))}
label="Choose an estimate system"
selected={estimateSystem}
onChange={(value) => setEstimateSystem(value)}
className="mb-4"
/>
</div>
<SubHeading>Choose a template</SubHeading>
<div className="flex flex-wrap gap-5 grid sm:grid-cols-2 mb-8">
{Object.keys(currentEstimateSystem.templates).map((name) => (
<button
key={name}
className=" border border-custom-border-200 rounded-md p-2 text-left"
onClick={() =>
setPoints(
currentEstimateSystem.templates[name].map((value: number | string, key: number) => ({
value,
key,
}))
)
}
>
<p className="block text-sm">{name}</p>
<p className="text-xs text-gray-400">{currentEstimateSystem.templates[name].join(", ")}</p>
</button>
))}
</div>
{/* Add modal footer */}
</form>
</Fragment>
)}
{points && (
<div>
<div className="flex justify-between items-center mb-6">
<div className="flex justify-start items-center">
<button onClick={() => setPoints(null)}>
<ChevronLeft className="w-6 h-6 mr-1" />
</button>
<SubHeading noMargin>New Estimate System</SubHeading>
</div>
<div>
<span className="text-xs text-gray-400">Step 2/2</span>
</div>
</div>
<Sortable
data={points}
render={(value: string, index: number) => (
<EstimateItem item={value} deleteItem={() => deleteItem(index)} />
)}
onChange={(data: number[]) => setPoints(data)}
keyExtractor={(value: number) => value}
/>
<button
className=" bg-custom-primary text-white rounded-md px-3 py-1 flex items-center gap-1"
onClick={() => {
setPoints([...points, ""]);
}}
>
<Plus className="w-4 h-4" />
<span className="text-sm">Add {estimateSystem}</span>
</button>
</div>
)}
<div className="flex justify-end mt-5 border-t -m-5 px-5 py-3 ">
<button
type="button"
onClick={handleClose}
className="inline-flex justify-center px-4 py-1 text-sm font-medium text-white bg-custom-primary border border-transparent rounded-md shadow-sm hover:bg-custom-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-custom-primary-dark"
>
Cancel
</button>
{points && (
<button
type="submit"
className="inline-flex justify-center px-4 py-1 ml-3 text-sm font-medium text-white bg-custom-primary border border-transparent rounded-md shadow-sm hover:bg-custom-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-custom-primary-dark"
onClick={saveEstimate}
>
Create Estimate
</button>
)}
</div>
</div>
</ModalCore>
);
});

View File

@ -5,7 +5,7 @@ import { Button } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// types
import { TEstimateSystemKeys, TEstimateSystemKeyObject } from "@/components/estimates/types";
import { TEstimatePointsObject } from "@/components/estimates/types";
type Props = {
isOpen: boolean;
@ -17,9 +17,7 @@ export const UpdateEstimateModal: FC<Props> = observer((props) => {
// props
const { handleClose, isOpen } = props;
// states
const [estimatePoints, setEstimatePoints] = useState<TEstimateSystemKeyObject[TEstimateSystemKeys] | undefined>(
undefined
);
const [estimatePoints, setEstimatePoints] = useState<TEstimatePointsObject[] | undefined>(undefined);
useEffect(() => {
if (!isOpen) {

View File

@ -17,7 +17,7 @@ import { IssueLabelSelect } from "@/components/issues/select";
// helpers
import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper";
// hooks
import { useEstimate } from "@/hooks/store";
import { useProjectEstimates } from "@/hooks/store";
type TInboxIssueProperties = {
projectId: string;
@ -29,7 +29,7 @@ type TInboxIssueProperties = {
export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props) => {
const { projectId, data, handleData, isVisible = false } = props;
// hooks
const { areEstimatesEnabledForProject } = useEstimate();
const { areEstimateEnabledByProjectId } = useProjectEstimates();
// states
const [parentIssueModalOpen, setParentIssueModalOpen] = useState(false);
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | undefined>(undefined);
@ -142,7 +142,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
)}
{/* estimate */}
{isVisible && areEstimatesEnabledForProject(projectId) && (
{isVisible && areEstimateEnabledByProjectId(projectId) && (
<div className="h-7">
<EstimateDropdown
value={data?.estimate_point || null}

View File

@ -2,7 +2,10 @@ import { FC } from "react";
import { observer } from "mobx-react";
import { Triangle } from "lucide-react";
// hooks
import { useEstimate, useIssueDetail } from "@/hooks/store";
import {
// useEstimate,
useIssueDetail,
} from "@/hooks/store";
// components
import { IssueActivityBlockComponent, IssueLink } from "./";
@ -14,13 +17,15 @@ export const IssueEstimateActivity: FC<TIssueEstimateActivity> = observer((props
const {
activity: { getActivityById },
} = useIssueDetail();
const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
// const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
const areEstimatesEnabledForCurrentProject = false;
const activity = getActivityById(activityId);
if (!activity) return <></>;
const estimateValue = getEstimatePointValue(Number(activity.new_value), null);
// const estimateValue = getEstimatePointValue(Number(activity.new_value), null);
const estimateValue = "None";
const currentPoint = Number(activity.new_value) + 1;
return (

View File

@ -54,7 +54,13 @@ import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
import { copyTextToClipboard } from "@/helpers/string.helper";
// types
import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "@/hooks/store";
import {
// useEstimate,
useIssueDetail,
useProject,
useProjectState,
useUser,
} from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// components
import type { TIssueOperations } from "./root";
@ -82,7 +88,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
// store hooks
const { getProjectById } = useProject();
const { data: currentUser } = useUser();
const { areEstimatesEnabledForCurrentProject } = useEstimate();
// const { areEstimatesEnabledForCurrentProject } = useEstimate();
const areEstimatesEnabledForCurrentProject = false;
const {
issue: { getIssueById },
} = useIssueDetail();

View File

@ -26,7 +26,7 @@ import { cn } from "@/helpers/common.helper";
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
// hooks
import { useEventTracker, useEstimate, useLabel, useIssues, useProjectState, useProject } from "@/hooks/store";
import { useEventTracker, useLabel, useIssues, useProjectState, useProject, useProjectEstimates } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// local components
import { IssuePropertyLabels } from "../properties/labels";
@ -42,6 +42,9 @@ export interface IIssueProperties {
}
export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { issue, updateIssue, displayProperties, activeLayout, isReadOnly, className } = props;
// store hooks
const { getProjectById } = useProject();
@ -53,13 +56,10 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
const {
issues: { addCycleToIssue, removeCycleFromIssue },
} = useIssues(EIssuesStoreType.CYCLE);
const { areEstimatesEnabledForCurrentProject } = useEstimate();
const { areEstimateEnabledByProjectId } = useProjectEstimates();
const { getStateById } = useProjectState();
const { isMobile } = usePlatformOS();
const projectDetails = getProjectById(issue.project_id);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const currentLayout = `${activeLayout} layout`;
// derived values
const stateDetails = getStateById(issue.state_id);
@ -394,7 +394,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
)}
{/* estimates */}
{areEstimatesEnabledForCurrentProject && (
{projectId && areEstimateEnabledByProjectId(projectId?.toString()) && (
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="estimate">
<div className="h-5" onClick={handleEventPropagation}>
<EstimateDropdown

View File

@ -28,7 +28,14 @@ import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"
import { getChangedIssuefields, getDescriptionPlaceholder } from "@/helpers/issue.helper";
import { shouldRenderProject } from "@/helpers/project.helper";
// hooks
import { useAppRouter, useEstimate, useInstance, useIssueDetail, useProject, useWorkspace } from "@/hooks/store";
import {
useAppRouter,
useInstance,
useIssueDetail,
useProject,
useWorkspace,
useProjectEstimates,
} from "@/hooks/store";
import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties";
// services
import { AIService } from "@/services/ai.service";
@ -118,7 +125,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const { projectId: routeProjectId } = useAppRouter();
const { config } = useInstance();
const { getProjectById } = useProject();
const { areEstimatesEnabledForProject } = useEstimate();
const { areEstimateEnabledByProjectId } = useProjectEstimates();
const {
issue: { getIssueById },
@ -631,7 +638,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
)}
/>
)}
{areEstimatesEnabledForProject(projectId) && (
{projectId && areEstimateEnabledByProjectId(projectId) && (
<Controller
control={control}
name="estimate_point"

View File

@ -1,4 +1,4 @@
import { Fragment, useState } from "react";
import { Fragment, Ref, useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
@ -6,7 +6,7 @@ import { usePopper } from "react-popper";
// icons
import { Check, ChevronDown, CircleUserRound, LogOut, Mails, PlusSquare, Settings, UserCircle2 } from "lucide-react";
// ui
import { Menu, Transition } from "@headlessui/react";
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from "@headlessui/react";
// types
import { IWorkspace } from "@plane/types";
// plane ui
@ -104,7 +104,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
<Menu as="div" className="relative h-full flex-grow truncate text-left">
{({ open }) => (
<>
<Menu.Button className="group/menu-button h-full w-full truncate rounded-md text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:outline-none">
<MenuButton className="group/menu-button h-full w-full truncate rounded-md text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:outline-none">
<div
className={`flex items-center gap-x-2 truncate rounded p-1 ${
sidebarCollapsed ? "justify-center" : "justify-between"
@ -126,7 +126,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
/>
)}
</div>
</Menu.Button>
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
@ -136,7 +136,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items as={Fragment}>
<MenuItems as={Fragment}>
<div className="fixed left-4 z-20 mt-1 flex w-full max-w-[19rem] origin-top-left flex-col divide-y divide-custom-border-100 rounded-md border-[0.5px] border-custom-sidebar-border-300 bg-custom-sidebar-background-100 shadow-custom-shadow-rg outline-none">
<div className="vertical-scrollbar scrollbar-sm mb-2 flex max-h-96 flex-col items-start justify-start gap-2 overflow-y-scroll px-4">
<h6 className="sticky top-0 z-10 h-full w-full bg-custom-sidebar-background-100 pb-1 pt-3 text-sm font-medium text-custom-sidebar-text-400">
@ -155,7 +155,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
}}
className="w-full"
>
<Menu.Item
<MenuItem
as="div"
className="flex items-center justify-between gap-1 rounded p-1 text-sm text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80"
>
@ -188,7 +188,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
<Check className="h-5 w-5 text-custom-sidebar-text-100" />
</span>
)}
</Menu.Item>
</MenuItem>
</Link>
))}
</div>
@ -203,13 +203,13 @@ export const WorkspaceSidebarDropdown = observer(() => {
</div>
<div className="flex w-full flex-col items-start justify-start gap-2 px-4 py-2 text-sm">
<Link href="/create-workspace" className="w-full">
<Menu.Item
<MenuItem
as="div"
className="flex items-center gap-2 rounded px-2 py-1 text-sm font-medium text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80"
>
<PlusSquare strokeWidth={1.75} className="h-4 w-4 flex-shrink-0" />
Create workspace
</Menu.Item>
</MenuItem>
</Link>
{userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Link
@ -220,18 +220,18 @@ export const WorkspaceSidebarDropdown = observer(() => {
if (index > 0) handleItemClick();
}}
>
<Menu.Item
<MenuItem
as="div"
className="flex items-center gap-2 rounded px-2 py-1 text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
>
<link.icon className="h-4 w-4 flex-shrink-0" />
{link.name}
</Menu.Item>
</MenuItem>
</Link>
))}
</div>
<div className="w-full px-4 py-2">
<Menu.Item
<MenuItem
as="button"
type="button"
className="flex w-full items-center gap-2 rounded px-2 py-1 text-sm font-medium text-red-600 hover:bg-custom-sidebar-background-80"
@ -239,17 +239,17 @@ export const WorkspaceSidebarDropdown = observer(() => {
>
<LogOut className="h-4 w-4 flex-shrink-0" />
Sign out
</Menu.Item>
</MenuItem>
</div>
</div>
</Menu.Items>
</MenuItems>
</Transition>
</>
)}
</Menu>
{!sidebarCollapsed && (
<Menu as="div" className="relative flex-shrink-0">
<Menu.Button className="grid place-items-center outline-none" ref={setReferenceElement}>
<MenuButton className="grid place-items-center outline-none" ref={setReferenceElement}>
<Avatar
name={currentUser?.display_name}
src={currentUser?.avatar || undefined}
@ -257,7 +257,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
shape="square"
className="!text-base"
/>
</Menu.Button>
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
@ -267,10 +267,10 @@ export const WorkspaceSidebarDropdown = observer(() => {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
<MenuItems
className="absolute left-0 z-20 mt-1 flex w-52 origin-top-left flex-col divide-y
divide-custom-sidebar-border-200 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 text-xs shadow-lg outline-none"
ref={setPopperElement}
ref={setPopperElement as Ref<HTMLDivElement>}
style={styles.popper}
{...attributes.popper}
>
@ -284,17 +284,17 @@ export const WorkspaceSidebarDropdown = observer(() => {
if (index == 0) handleItemClick();
}}
>
<Menu.Item key={index} as="div">
<MenuItem key={index} as="div">
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
<link.icon className="h-4 w-4 stroke-[1.5]" />
{link.name}
</span>
</Menu.Item>
</MenuItem>
</Link>
))}
</div>
<div className={`pt-2 ${isUserInstanceAdmin || false ? "pb-2" : ""}`}>
<Menu.Item
<MenuItem
as="button"
type="button"
className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"
@ -302,20 +302,20 @@ export const WorkspaceSidebarDropdown = observer(() => {
>
<LogOut className="h-4 w-4 stroke-[1.5]" />
Sign out
</Menu.Item>
</MenuItem>
</div>
{isUserInstanceAdmin && (
<div className="p-2 pb-0">
<Link href={GOD_MODE_URL}>
<Menu.Item as="button" type="button" className="w-full">
<MenuItem as="button" type="button" className="w-full">
<span className="flex w-full items-center justify-center rounded bg-custom-primary-100/20 px-2 py-1 text-sm font-medium text-custom-primary-100 hover:bg-custom-primary-100/30 hover:text-custom-primary-200">
Enter God Mode
</span>
</Menu.Item>
</MenuItem>
</Link>
</div>
)}
</Menu.Items>
</MenuItems>
</Transition>
</Menu>
)}

View File

@ -7,39 +7,39 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = {
fibonacci: {
title: "Fibonacci",
values: [
{ key: 1, value: 1 },
{ key: 2, value: 2 },
{ key: 3, value: 3 },
{ key: 4, value: 5 },
{ key: 5, value: 8 },
{ key: 6, value: 13 },
{ key: 7, value: 21 },
{ id: undefined, key: 1, value: "1" },
{ id: undefined, key: 2, value: "2" },
{ id: undefined, key: 3, value: "3" },
{ id: undefined, key: 4, value: "5" },
{ id: undefined, key: 5, value: "8" },
{ id: undefined, key: 6, value: "13" },
{ id: undefined, key: 7, value: "21" },
],
},
linear: {
title: "Linear",
values: [
{ key: 1, value: 1 },
{ key: 2, value: 2 },
{ key: 3, value: 3 },
{ key: 4, value: 4 },
{ key: 5, value: 5 },
{ key: 6, value: 6 },
{ key: 7, value: 7 },
{ key: 8, value: 8 },
{ key: 9, value: 9 },
{ key: 10, value: 10 },
{ id: undefined, key: 1, value: "1" },
{ id: undefined, key: 2, value: "2" },
{ id: undefined, key: 3, value: "3" },
{ id: undefined, key: 4, value: "4" },
{ id: undefined, key: 5, value: "5" },
{ id: undefined, key: 6, value: "6" },
{ id: undefined, key: 7, value: "7" },
{ id: undefined, key: 8, value: "8" },
{ id: undefined, key: 9, value: "9" },
{ id: undefined, key: 10, value: "10" },
],
},
squares: {
title: "Squares",
values: [
{ key: 1, value: 1 },
{ key: 2, value: 4 },
{ key: 3, value: 9 },
{ key: 4, value: 16 },
{ key: 5, value: 25 },
{ key: 6, value: 36 },
{ id: undefined, key: 1, value: "1" },
{ id: undefined, key: 2, value: "4" },
{ id: undefined, key: 3, value: "9" },
{ id: undefined, key: 4, value: "16" },
{ id: undefined, key: 5, value: "25" },
{ id: undefined, key: 6, value: "36" },
],
},
},
@ -51,21 +51,21 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = {
t_shirt_sizes: {
title: "T-Shirt Sizes",
values: [
{ key: 1, value: "XS" },
{ key: 2, value: "S" },
{ key: 3, value: "M" },
{ key: 4, value: "L" },
{ key: 5, value: "XL" },
{ key: 6, value: "XXL" },
{ id: undefined, key: 1, value: "XS" },
{ id: undefined, key: 2, value: "S" },
{ id: undefined, key: 3, value: "M" },
{ id: undefined, key: 4, value: "L" },
{ id: undefined, key: 5, value: "XL" },
{ id: undefined, key: 6, value: "XXL" },
],
},
easy_to_hard: {
title: "Easy to hard",
values: [
{ key: 1, value: "Easy" },
{ key: 2, value: "Medium" },
{ key: 3, value: "Hard" },
{ key: 4, value: "Very Hard" },
{ id: undefined, key: 1, value: "Easy" },
{ id: undefined, key: 2, value: "Medium" },
{ id: undefined, key: 3, value: "Hard" },
{ id: undefined, key: 4, value: "Very Hard" },
],
},
},
@ -77,16 +77,16 @@ export const ESTIMATE_SYSTEMS: TEstimateSystems = {
hours: {
title: "Hours",
values: [
{ key: 1, value: 1 },
{ key: 2, value: 2 },
{ key: 3, value: 3 },
{ key: 4, value: 4 },
{ key: 5, value: 5 },
{ key: 6, value: 6 },
{ key: 7, value: 7 },
{ key: 8, value: 8 },
{ key: 9, value: 9 },
{ key: 10, value: 10 },
{ id: undefined, key: 1, value: "1" },
{ id: undefined, key: 2, value: "2" },
{ id: undefined, key: 3, value: "3" },
{ id: undefined, key: 4, value: "4" },
{ id: undefined, key: 5, value: "5" },
{ id: undefined, key: 6, value: "6" },
{ id: undefined, key: 7, value: "7" },
{ id: undefined, key: 8, value: "8" },
{ id: undefined, key: 9, value: "9" },
{ id: undefined, key: 10, value: "10" },
],
},
},

View File

@ -4,11 +4,9 @@ import { StoreContext } from "@/lib/store-context";
// mobx store
import { IProjectEstimateStore } from "@/store/estimates/project-estimate.store";
export const useProjectEstimates = (projectId: string | undefined): IProjectEstimateStore => {
export const useProjectEstimates = (): IProjectEstimateStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useProjectPage must be used within StoreProvider");
if (!projectId) throw new Error("projectId must be passed as a property");
return context.projectEstimate;
};

View File

@ -1,4 +1,4 @@
import { useCycle, useEstimate, useLabel, useMember, useModule, useProjectState } from "./store";
import { useCycle, useProjectEstimates, useLabel, useMember, useModule, useProjectState } from "./store";
export const useProjectIssueProperties = () => {
const { fetchProjectStates } = useProjectState();
@ -8,7 +8,7 @@ export const useProjectIssueProperties = () => {
const { fetchProjectLabels } = useLabel();
const { fetchAllCycles: fetchProjectAllCycles } = useCycle();
const { fetchModules: fetchProjectAllModules } = useModule();
const { fetchProjectEstimates } = useEstimate();
const { getProjectEstimates } = useProjectEstimates();
// fetching project states
const fetchStates = async (
@ -62,7 +62,7 @@ export const useProjectIssueProperties = () => {
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectEstimates(workspaceSlug.toString(), projectId.toString());
await getProjectEstimates(workspaceSlug.toString(), projectId.toString());
}
};

View File

@ -1,12 +1,12 @@
import useSWR from "swr";
import { useCycle, useEstimate, useLabel, useModule, useProjectState } from "./store";
import { useCycle, useProjectEstimates, useLabel, useModule, useProjectState } from "./store";
export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | undefined) => {
const { fetchWorkspaceLabels } = useLabel();
const { fetchWorkspaceStates } = useProjectState();
const { fetchWorkspaceEstimates } = useEstimate();
const { getWorkspaceEstimates } = useProjectEstimates();
const { fetchWorkspaceModules } = useModule();
@ -43,7 +43,7 @@ export const useWorkspaceIssueProperties = (workspaceSlug: string | string[] | u
// fetch workspace estimates
useSWR(
workspaceSlug ? `WORKSPACE_ESTIMATES_${workspaceSlug}` : null,
workspaceSlug ? () => fetchWorkspaceEstimates(workspaceSlug.toString()) : null,
workspaceSlug ? () => getWorkspaceEstimates(workspaceSlug.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
};

View File

@ -9,7 +9,7 @@ import { EmptyState, LogoSpinner } from "@/components/common";
import {
useEventTracker,
useCycle,
useEstimate,
useProjectEstimates,
useLabel,
useMember,
useModule,
@ -44,7 +44,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
} = useMember();
const { fetchProjectStates } = useProjectState();
const { fetchProjectLabels } = useLabel();
const { fetchProjectEstimates } = useEstimate();
const { getProjectEstimates } = useProjectEstimates();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -80,7 +80,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
// fetching project estimates
useSWR(
workspaceSlug && projectId ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectEstimates(workspaceSlug.toString(), projectId.toString()) : null,
workspaceSlug && projectId ? () => getProjectEstimates(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project cycles

View File

@ -43,8 +43,6 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
return (
<>
{/* TODO: Need to handle custom themes for toast */}
<Toast theme={resolveGeneralTheme(resolvedTheme)} />
<InstanceWrapper>
<StoreWrapper>
<CrispWrapper user={currentUser}>
@ -56,7 +54,11 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
posthogAPIKey={config?.posthog_api_key || undefined}
posthogHost={config?.posthog_host || undefined}
>
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
<SWRConfig value={SWR_CONFIG}>
{/* TODO: Need to handle custom themes for toast */}
<Toast theme={resolveGeneralTheme(resolvedTheme)} />
{children}
</SWRConfig>
</PostHogProvider>
</CrispWrapper>
</StoreWrapper>

View File

@ -35,24 +35,24 @@ const nextConfig = {
{
source: "/accounts/sign-up",
destination: "/sign-up",
permanent: true
permanent: true,
},
{
source: "/sign-in",
destination: "/",
permanent: true
permanent: true,
},
{
source: "/register",
destination: "/sign-up",
permanent: true
permanent: true,
},
{
source: "/login",
destination: "/",
permanent: true
}
]
permanent: true,
},
];
},
async rewrites() {
const rewrites = [
@ -66,13 +66,13 @@ const nextConfig = {
},
];
if (process.env.NEXT_PUBLIC_ADMIN_BASE_URL || process.env.NEXT_PUBLIC_ADMIN_BASE_PATH) {
const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || ""
const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || ""
const GOD_MODE_BASE_URL = ADMIN_BASE_URL + ADMIN_BASE_PATH
const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || "";
const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "";
const GOD_MODE_BASE_URL = ADMIN_BASE_URL + ADMIN_BASE_PATH;
rewrites.push({
source: "/god-mode/:path*",
destination: `${GOD_MODE_BASE_URL}/:path*`,
})
});
}
return rewrites;
},

View File

@ -25,12 +25,12 @@
"@nivo/line": "0.80.0",
"@nivo/pie": "0.80.0",
"@nivo/scatterplot": "0.80.0",
"@plane/constants": "*",
"@plane/document-editor": "*",
"@plane/lite-text-editor": "*",
"@plane/rich-text-editor": "*",
"@plane/types": "*",
"@plane/ui": "*",
"@plane/constants": "*",
"@popperjs/core": "^2.11.8",
"@sentry/nextjs": "^7.108.0",
"axios": "^1.1.3",
@ -70,17 +70,17 @@
"@types/dompurify": "^3.0.5",
"@types/js-cookie": "^3.0.2",
"@types/lodash": "^4.14.202",
"@types/node": "18.0.6",
"@types/node": "18.16.1",
"@types/nprogress": "^0.2.0",
"@types/react": "^18.2.42",
"@types/react": "^18.2.48",
"@types/react-color": "^3.0.6",
"@types/react-dom": "^18.2.17",
"@types/react-dom": "^18.2.18",
"@types/uuid": "^8.3.4",
"@types/zxcvbn": "^4.4.4",
"eslint-config-custom": "*",
"prettier": "^3.2.5",
"tailwind-config-custom": "*",
"tsconfig": "*",
"typescript": "4.7.4"
"typescript": "^5.4.2"
}
}

View File

@ -1,5 +1,5 @@
// types
import { IEstimate } from "@plane/types";
import { IEstimate, IEstimateFormData } from "@plane/types";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
// services
@ -11,7 +11,7 @@ export class EstimateService extends APIService {
}
// fetching the estimates in workspace level
async fetchWorkspacesList(workspaceSlug: string): Promise<IEstimate[] | undefined> {
async fetchWorkspaceEstimates(workspaceSlug: string): Promise<IEstimate[] | undefined> {
try {
const { data } = await this.get(`/api/workspaces/${workspaceSlug}/estimates/`);
return data || undefined;
@ -20,7 +20,7 @@ export class EstimateService extends APIService {
}
}
async fetchAll(workspaceSlug: string, projectId: string): Promise<IEstimate[] | undefined> {
async fetchProjectEstimates(workspaceSlug: string, projectId: string): Promise<IEstimate[] | undefined> {
try {
const { data } = await this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`);
return data || undefined;
@ -29,7 +29,11 @@ export class EstimateService extends APIService {
}
}
async fetchById(workspaceSlug: string, projectId: string, estimateId: string): Promise<IEstimate | undefined> {
async fetchEstimateById(
workspaceSlug: string,
projectId: string,
estimateId: string
): Promise<IEstimate | undefined> {
try {
const { data } = await this.get(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`
@ -40,7 +44,11 @@ export class EstimateService extends APIService {
}
}
async create(workspaceSlug: string, projectId: string, payload: Partial<IEstimate>): Promise<IEstimate | undefined> {
async createEstimate(
workspaceSlug: string,
projectId: string,
payload: IEstimateFormData
): Promise<IEstimate | undefined> {
try {
const { data } = await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, payload);
return data || undefined;
@ -49,11 +57,11 @@ export class EstimateService extends APIService {
}
}
async update(
async updateEstimate(
workspaceSlug: string,
projectId: string,
estimateId: string,
payload: Partial<IEstimate>
payload: IEstimateFormData
): Promise<IEstimate | undefined> {
try {
const { data } = await this.patch(
@ -66,14 +74,20 @@ export class EstimateService extends APIService {
}
}
async remove(workspaceSlug: string, projectId: string, estimateId: string): Promise<void> {
try {
const { data } = await this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`
);
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
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}

View File

@ -1,222 +0,0 @@
import set from "lodash/set";
import { observable, action, makeObservable, runInAction, computed } from "mobx";
import { computedFn } from "mobx-utils";
// types
import { IEstimate, IEstimateFormData } from "@plane/types";
// services
import { ProjectEstimateService } from "@/services/project";
// store
import { RootStore } from "@/store/root.store";
export interface IEstimateStore {
//Loaders
fetchedMap: Record<string, boolean>;
// observables
estimateMap: Record<string, IEstimate>;
// computed
areEstimatesEnabledForCurrentProject: boolean;
projectEstimates: IEstimate[] | null;
activeEstimateDetails: IEstimate | null;
// computed actions
areEstimatesEnabledForProject: (projectId: string) => boolean;
getEstimatePointValue: (estimateKey: number | null, projectId: string | null) => string;
getProjectEstimateById: (estimateId: string) => IEstimate | null;
getProjectActiveEstimateDetails: (projectId: string) => IEstimate | null;
// fetch actions
fetchProjectEstimates: (workspaceSlug: string, projectId: string) => Promise<IEstimate[]>;
fetchWorkspaceEstimates: (workspaceSlug: string) => Promise<IEstimate[]>;
// crud actions
createEstimate: (workspaceSlug: string, projectId: string, data: IEstimateFormData) => Promise<IEstimate>;
updateEstimate: (
workspaceSlug: string,
projectId: string,
estimateId: string,
data: IEstimateFormData
) => Promise<IEstimate>;
deleteEstimate: (workspaceSlug: string, projectId: string, estimateId: string) => Promise<void>;
}
export class EstimateStore implements IEstimateStore {
// observables
estimateMap: Record<string, IEstimate> = {};
//loaders
fetchedMap: Record<string, boolean> = {};
// root store
rootStore;
// services
estimateService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observables
estimateMap: observable,
fetchedMap: observable,
// computed
areEstimatesEnabledForCurrentProject: computed,
projectEstimates: computed,
activeEstimateDetails: computed,
// actions
fetchProjectEstimates: action,
fetchWorkspaceEstimates: action,
createEstimate: action,
updateEstimate: action,
deleteEstimate: action,
});
// root store
this.rootStore = _rootStore;
// services
this.estimateService = new ProjectEstimateService();
}
/**
* @description returns true if estimates are enabled for current project, false otherwise
*/
get areEstimatesEnabledForCurrentProject() {
const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails;
if (!currentProjectDetails) return false;
return Boolean(currentProjectDetails?.estimate);
}
/**
* @description returns the list of estimates for current project
*/
get projectEstimates() {
const projectId = this.rootStore.router.projectId;
const worksapceSlug = this.rootStore.router.workspaceSlug || "";
if (!projectId || !(this.fetchedMap[projectId] || this.fetchedMap[worksapceSlug])) return null;
return Object.values(this.estimateMap).filter((estimate) => estimate.project === projectId);
}
/**
* @description returns the active estimate details for current project
*/
get activeEstimateDetails() {
const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails;
if (!currentProjectDetails || !currentProjectDetails?.estimate) return null;
return this.estimateMap?.[currentProjectDetails?.estimate || ""] || null;
}
/**
* @description returns true if estimates are enabled for a project using project id
* @param projectId
*/
areEstimatesEnabledForProject = computedFn((projectId: string) => {
const projectDetails = this.rootStore.projectRoot.project.getProjectById(projectId);
if (!projectDetails) return false;
return Boolean(projectDetails.estimate) ?? false;
});
/**
* @description returns the point value for the given estimate key to display in the UI
*/
getEstimatePointValue = computedFn((estimateKey: number | null, projectId: string | null) => {
if (estimateKey === null) return "None";
const activeEstimate = projectId ? this.getProjectActiveEstimateDetails(projectId) : this.activeEstimateDetails;
return activeEstimate?.points?.find((point) => point.key === estimateKey)?.value || "None";
});
/**
* @description returns the estimate details for the given estimate id
* @param estimateId
*/
getProjectEstimateById = computedFn((estimateId: string) => {
if (!this.projectEstimates) return null;
const estimateInfo = this.estimateMap?.[estimateId] || null;
return estimateInfo;
});
/**
* @description returns the estimate details for the given estimate id
* @param projectId
*/
getProjectActiveEstimateDetails = computedFn((projectId: string) => {
const projectDetails = this.rootStore.projectRoot.project?.getProjectById(projectId);
const worksapceSlug = this.rootStore.router.workspaceSlug || "";
if (!projectDetails || !projectDetails?.estimate || !(this.fetchedMap[projectId] || this.fetchedMap[worksapceSlug]))
return null;
return this.estimateMap?.[projectDetails?.estimate || ""] || null;
});
/**
* @description fetches the list of estimates for the given project
* @param workspaceSlug
* @param projectId
*/
fetchProjectEstimates = async (workspaceSlug: string, projectId: string) =>
await this.estimateService.getEstimatesList(workspaceSlug, projectId).then((response) => {
runInAction(() => {
response.forEach((estimate) => {
set(this.estimateMap, estimate.id, estimate);
});
this.fetchedMap[projectId] = true;
});
return response;
});
/**
* @description fetches the list of estimates for the given project
* @param workspaceSlug
* @param projectId
*/
fetchWorkspaceEstimates = async (workspaceSlug: string) =>
await this.estimateService.getWorkspaceEstimatesList(workspaceSlug).then((response) => {
runInAction(() => {
response.forEach((estimate) => {
set(this.estimateMap, estimate.id, estimate);
});
this.fetchedMap[workspaceSlug] = true;
});
return response;
});
/**
* @description creates a new estimate for the given project
* @param workspaceSlug
* @param projectId
* @param data
*/
createEstimate = async (workspaceSlug: string, projectId: string, data: IEstimateFormData) =>
await this.estimateService.createEstimate(workspaceSlug, projectId, data).then((response) => {
const responseEstimate = {
...response.estimate,
points: response.estimate_points,
};
runInAction(() => {
set(this.estimateMap, [responseEstimate.id], responseEstimate);
});
return response.estimate;
});
/**
* @description updates the given estimate for the given project
* @param workspaceSlug
* @param projectId
* @param estimateId
* @param data
*/
updateEstimate = async (workspaceSlug: string, projectId: string, estimateId: string, data: IEstimateFormData) =>
await this.estimateService.patchEstimate(workspaceSlug, projectId, estimateId, data).then((response) => {
runInAction(() => {
set(this.estimateMap, estimateId, {
...this.estimateMap[estimateId],
...data.estimate,
points: [...data.estimate_points],
});
});
return response;
});
/**
* @description deletes the given estimate for the given project
* @param workspaceSlug
* @param projectId
* @param estimateId
*/
deleteEstimate = async (workspaceSlug: string, projectId: string, estimateId: string) =>
await this.estimateService.deleteEstimate(workspaceSlug, projectId, estimateId).then(() => {
runInAction(() => {
delete this.estimateMap[estimateId];
});
});
}

View File

@ -1,5 +1,5 @@
import { action, computed, makeObservable, observable } from "mobx";
import { IEstimatePoint as IEstimatePointType } from "@plane/types";
import { IEstimateFormData, IEstimate, IEstimatePoint as IEstimatePointType } from "@plane/types";
// services
import { EstimateService } from "@/services/project/estimate.service";
// store
@ -16,8 +16,7 @@ export interface IEstimatePoint extends IEstimatePointType {
// computed
asJson: IEstimatePointType;
// actions
updateEstimatePoint: () => Promise<void>;
deleteEstimatePoint: () => Promise<void>;
updateEstimatePoint: (payload: IEstimateFormData) => Promise<void>;
}
export class EstimatePoint implements IEstimatePoint {
@ -40,6 +39,7 @@ export class EstimatePoint implements IEstimatePoint {
constructor(
private store: RootStore,
private projectEstimate: IEstimate,
private data: IEstimatePointType
) {
makeObservable(this, {
@ -61,7 +61,6 @@ export class EstimatePoint implements IEstimatePoint {
asJson: computed,
// actions
updateEstimatePoint: action,
deleteEstimatePoint: action,
});
this.id = this.data.id;
this.key = this.data.key;
@ -97,19 +96,10 @@ export class EstimatePoint implements IEstimatePoint {
}
// actions
updateEstimatePoint = async () => {
updateEstimatePoint = async (payload: IEstimateFormData) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !this.id) return undefined;
} catch (error) {
throw error;
}
};
deleteEstimatePoint = async () => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !this.id) return;
if (!workspaceSlug || !projectId || !this.projectEstimate?.id || !this.id || !payload) return undefined;
} catch (error) {
throw error;
}

View File

@ -1,5 +1,7 @@
import set from "lodash/set";
import { action, computed, makeObservable, observable } from "mobx";
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,
@ -7,6 +9,7 @@ import {
IProject,
IWorkspace,
TEstimateType,
IEstimateFormData,
} from "@plane/types";
// services
import { EstimateService } from "@/services/project/estimate.service";
@ -27,10 +30,9 @@ export interface IEstimate extends IEstimateType {
asJson: IEstimateType;
EstimatePointIds: string[] | undefined;
estimatePointById: (estimateId: string) => IEstimatePointType | undefined;
// helper actions
// actions
updateEstimate: (data: Partial<IEstimateType>) => Promise<IEstimateType | undefined>;
deleteEstimatePoint: (data: Partial<IEstimateType>) => Promise<void>;
updateEstimatePointSorting: (payload: IEstimateFormData) => Promise<void>;
deleteEstimatePoint: (estimatePointId: string) => Promise<void>;
}
export class Estimate implements IEstimate {
@ -80,7 +82,7 @@ export class Estimate implements IEstimate {
asJson: computed,
EstimatePointIds: computed,
// actions
updateEstimate: action,
updateEstimatePointSorting: action,
deleteEstimatePoint: action,
});
this.id = this.data.id;
@ -99,7 +101,7 @@ export class Estimate implements IEstimate {
this.data.points?.forEach((estimationPoint) => {
if (estimationPoint.id)
set(this.estimatePoints, [estimationPoint.id], new EstimatePoint(this.store, estimationPoint));
set(this.estimatePoints, [estimationPoint.id], new EstimatePoint(this.store, this.data, estimationPoint));
});
// service
this.service = new EstimateService();
@ -141,26 +143,37 @@ export class Estimate implements IEstimate {
});
// actions
updateEstimate = async (estimatePoint: Partial<IEstimateType>) => {
updateEstimatePointSorting = async (payload: IEstimateFormData) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !this.id) return undefined;
if (!workspaceSlug || !projectId || !this.id || !payload) return;
Object.entries(estimatePoint ?? {}).forEach(([key, value]) => {
if (!key || !value) return;
set(this, key, value);
// make update estimation request
// runInAction(() => {
// this.points = payload.estimate_points;
// this.data.points = payload.estimate_points;
// });
} catch (error) {
throw error;
}
};
deleteEstimatePoint = async (estimatePointId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !estimatePointId) return;
// make delete estimation request
runInAction(() => {
update(this, "points", (estimationPoints = []) =>
estimationPoints.filter((point: IEstimatePointType) => point.id !== estimatePointId)
);
unset(this.estimatePoints, [estimatePointId]);
});
} catch (error) {
throw error;
}
};
deleteEstimatePoint = async () => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !this.id) return;
} catch (error) {
throw error;
}
};
}

View File

@ -1,9 +1,8 @@
import set from "lodash/set";
import unset from "lodash/unset";
import update from "lodash/update";
import { action, computed, makeObservable, observable } from "mobx";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { IEstimate as IEstimateType } from "@plane/types";
import { IEstimate as IEstimateType, IEstimateFormData } from "@plane/types";
// services
import { EstimateService } from "@/services/project/estimate.service";
// store
@ -22,14 +21,23 @@ export interface IProjectEstimateStore {
estimates: Record<string, IEstimate>;
error: TErrorCodes | undefined;
// computed
areEstimateEnabledByProjectId: (projectId: string) => boolean;
projectEstimateIds: string[] | undefined;
estimateIdsByProjectId: (projectId: string) => string[] | undefined;
estimateById: (estimateId: string) => IEstimate | undefined;
// actions
getWorkspaceAllEstimates: () => Promise<IEstimateType[] | undefined>;
getAllEstimates: () => Promise<IEstimateType[] | undefined>;
getEstimateById: (estimateId: string) => Promise<IEstimateType | undefined>;
createEstimate: (data: Partial<IEstimateType>) => Promise<IEstimateType | undefined>;
deleteEstimate: (estimateId: string) => Promise<void>;
getWorkspaceEstimates: (workspaceSlug: string, loader?: TEstimateLoader) => Promise<IEstimateType[] | undefined>;
getProjectEstimates: (
workspaceSlug: string,
projectId: string,
loader?: TEstimateLoader
) => Promise<IEstimateType[] | undefined>;
getEstimateById: (workspaceSlug: string, projectId: string, estimateId: string) => Promise<IEstimateType | undefined>;
createEstimate: (
workspaceSlug: string,
projectId: string,
data: IEstimateFormData
) => Promise<IEstimateType | undefined>;
}
export class ProjectEstimateStore implements IProjectEstimateStore {
@ -49,28 +57,56 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
// computed
projectEstimateIds: computed,
// actions
getWorkspaceAllEstimates: action,
getAllEstimates: action,
getWorkspaceEstimates: action,
getProjectEstimates: action,
getEstimateById: action,
createEstimate: action,
deleteEstimate: action,
});
// service
this.service = new EstimateService();
}
// computed
get projectEstimateIds() {
/**
* @description get estimates are enabled in the project or not
* @returns { boolean }
*/
areEstimateEnabledByProjectId = computedFn((projectId: string) => {
if (!projectId) return false;
const projectDetails = this.store.projectRoot.project.getProjectById(projectId);
if (!projectDetails) return false;
return Boolean(projectDetails.estimate) || false;
});
/**
* @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 && p != undefined) as string[];
return projectEstimatesIds ?? undefined;
}
/**
* @description get all estimate ids for a project
* @returns { string[] | undefined }
*/
estimateIdsByProjectId = computedFn((projectId: string) => {
if (!projectId) return undefined;
const projectEstimatesIds = Object.values(this.estimates || {})
.filter((p) => p.project === projectId)
.map((p) => p.id && p != undefined) as string[];
return projectEstimatesIds ?? undefined;
});
/**
* @description get estimate by id
* @returns { IEstimate | undefined }
*/
estimateById = computedFn((estimateId: string) => {
if (!estimateId) return undefined;
return this.estimates[estimateId] ?? undefined;
@ -78,24 +114,29 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
// actions
/**
* @description fetch all estimates for a project
* @description fetch all estimates for a workspace
* @returns { IEstimateType[] | undefined }
*/
getWorkspaceAllEstimates = async (): Promise<IEstimateType[] | undefined> => {
getWorkspaceEstimates = async (
workspaceSlug: string,
loader: TEstimateLoader = "mutation-loader"
): Promise<IEstimateType[] | undefined> => {
try {
const { workspaceSlug } = this.store.router;
if (!workspaceSlug) return;
this.error = undefined;
const estimates = await this.service.fetchWorkspacesList(workspaceSlug);
if (!this.projectEstimateIds) this.loader = loader ? loader : "init-loader";
if (estimates && estimates.length > 0)
const estimates = await this.service.fetchWorkspaceEstimates(workspaceSlug);
if (estimates && estimates.length > 0) {
runInAction(() => {
estimates.forEach((estimate) => {
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
});
});
}
return estimates;
} catch (error) {
this.loader = undefined;
this.error = {
status: "error",
message: "Error fetching estimates",
@ -103,21 +144,31 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
}
};
getAllEstimates = async (): Promise<IEstimateType[] | undefined> => {
/**
* @description fetch all estimates for a project
* @returns { IEstimateType[] | undefined }
*/
getProjectEstimates = async (
workspaceSlug: string,
projectId: string,
loader: TEstimateLoader = "mutation-loader"
): Promise<IEstimateType[] | undefined> => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId) return;
this.error = undefined;
const estimates = await this.service.fetchAll(workspaceSlug, projectId);
if (!this.projectEstimateIds) this.loader = loader ? loader : "init-loader";
if (estimates && estimates.length > 0)
const estimates = await this.service.fetchProjectEstimates(workspaceSlug, projectId);
if (estimates && estimates.length > 0) {
runInAction(() => {
estimates.forEach((estimate) => {
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
});
});
}
return estimates;
} catch (error) {
this.loader = undefined;
this.error = {
status: "error",
message: "Error fetching estimates",
@ -130,19 +181,24 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
* @param { string } estimateId
* @returns IEstimateType | undefined
*/
getEstimateById = async (estimateId: string): Promise<IEstimateType | undefined> => {
getEstimateById = async (
workspaceSlug: string,
projectId: string,
estimateId: string
): Promise<IEstimateType | undefined> => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId) return;
this.error = undefined;
const estimate = await this.service.fetchById(workspaceSlug, projectId, estimateId);
if (estimate && estimate.id)
const estimate = await this.service.fetchEstimateById(workspaceSlug, projectId, estimateId);
if (estimate) {
runInAction(() => {
if (estimate.id)
update(this.estimates, [estimate.id], (estimateStore) => {
if (estimateStore) estimateStore.updateEstimate(estimate);
else return new Estimate(this.store, estimate);
});
});
}
return estimate;
} catch (error) {
@ -155,48 +211,30 @@ export class ProjectEstimateStore implements IProjectEstimateStore {
/**
* @description create an estimate for a project
* @param { Partial<IEstimateType> } data
* @param { Partial<IEstimateType> } payload
* @returns
*/
createEstimate = async (data: Partial<IEstimateType>): Promise<IEstimateType | undefined> => {
createEstimate = async (
workspaceSlug: string,
projectId: string,
payload: IEstimateFormData
): Promise<IEstimateType | undefined> => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId) return;
this.error = undefined;
const estimate = await this.service.create(workspaceSlug, projectId, data);
if (estimate && estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
const estimate = await this.service.createEstimate(workspaceSlug, projectId, payload);
if (estimate) {
runInAction(() => {
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
});
}
return estimate;
} catch (error) {
console.error("Error creating estimate");
this.error = {
status: "error",
message: "Error creating estimate",
};
}
};
/**
* @description delete an estimate for a project
* @param { string } estimateId
* @returns void
*/
deleteEstimate = async (estimateId: string): Promise<void> => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId) return;
this.error = undefined;
await this.service.remove(workspaceSlug, projectId, estimateId);
unset(this.estimates, [estimateId]);
} catch (error) {
console.error("Error deleting estimate");
this.error = {
status: "error",
message: "Error deleting estimate",
};
}
};
}

View File

@ -346,7 +346,10 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
// fetching other project modules
this.rootIssueDetailStore.rootIssueStore.rootStore.module.fetchModules(workspaceSlug, projectId);
// fetching other project estimates
this.rootIssueDetailStore.rootIssueStore.rootStore.estimate.fetchProjectEstimates(workspaceSlug, projectId);
this.rootIssueDetailStore.rootIssueStore.rootStore.projectEstimate.getProjectEstimates(
workspaceSlug,
projectId
);
}
}
} catch (error) {

View File

@ -4,7 +4,6 @@ import { CommandPaletteStore, ICommandPaletteStore } from "./command-palette.sto
import { CycleStore, ICycleStore } from "./cycle.store";
import { CycleFilterStore, ICycleFilterStore } from "./cycle_filter.store";
import { DashboardStore, IDashboardStore } from "./dashboard.store";
import { EstimateStore, IEstimateStore } from "./estimate.store";
import { IProjectEstimateStore, ProjectEstimateStore } from "./estimates/project-estimate.store";
import { EventTrackerStore, IEventTrackerStore } from "./event-tracker.store";
import { GlobalViewStore, IGlobalViewStore } from "./global-view.store";
@ -39,7 +38,6 @@ export class RootStore {
issue: IIssueRootStore;
state: IStateStore;
label: ILabelStore;
estimate: IEstimateStore;
dashboard: IDashboardStore;
projectPages: IProjectPageStore;
router: IRouterStore;
@ -66,7 +64,6 @@ export class RootStore {
this.issue = new IssueRootStore(this);
this.state = new StateStore(this);
this.label = new LabelStore(this);
this.estimate = new EstimateStore(this);
this.dashboard = new DashboardStore(this);
this.commandPalette = new CommandPaletteStore();
this.theme = new ThemeStore(this);
@ -95,7 +92,6 @@ export class RootStore {
this.issue = new IssueRootStore(this);
this.state = new StateStore(this);
this.label = new LabelStore(this);
this.estimate = new EstimateStore(this);
this.dashboard = new DashboardStore(this);
this.router = new RouterStore();
this.commandPalette = new CommandPaletteStore();

659
yarn.lock

File diff suppressed because it is too large Load Diff