Skip to content

Commit

Permalink
feat: added estimates (#721)
Browse files Browse the repository at this point in the history
* feat: added estimates

* chore: added estimate to issue sidebar
  • Loading branch information
kunalv17 authored Apr 6, 2023
1 parent 14dd498 commit 95fe4a3
Show file tree
Hide file tree
Showing 18 changed files with 1,288 additions and 2 deletions.
205 changes: 205 additions & 0 deletions apps/app/components/estimates/create-update-estimate-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import React, { useEffect } from "react";

import { useRouter } from "next/router";

import { mutate } from "swr";

// react-hook-form
import { useForm } from "react-hook-form";
// services
import estimatesService from "services/estimates.service";
// ui
import { Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui";
import { Dialog, Transition } from "@headlessui/react";

// hooks
import useToast from "hooks/use-toast";

// types
import { IEstimate } from "types";
// fetch-keys
import { ESTIMATES_LIST } from "constants/fetch-keys";

type Props = {
handleClose: () => void;
data?: IEstimate;
isOpen: boolean;
isCreate: boolean;
};

const defaultValues: Partial<IEstimate> = {
name: "",
description: "",
};

export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data, isOpen, isCreate }) => {
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
reset,
} = useForm<IEstimate>({
defaultValues,
});

const router = useRouter();
const { workspaceSlug, projectId } = router.query;

const { setToastAlert } = useToast();

const createEstimate = async (formData: IEstimate) => {
if (!workspaceSlug || !projectId) return;

const payload = {
name: formData.name,
description: formData.description,
};

await estimatesService
.createEstimate(workspaceSlug as string, projectId as string, payload)
.then((res) => {
mutate<IEstimate[]>(
ESTIMATES_LIST(projectId as string),
(prevData) => [res, ...(prevData ?? [])],
false
);
handleClose();
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Error: Estimate could not be created",
});
});
};

const updateEstimate = async (formData: IEstimate) => {
if (!workspaceSlug || !projectId || !data) return;

const payload = {
name: formData.name,
description: formData.description,
};

mutate<IEstimate[]>(
ESTIMATES_LIST(projectId as string),
(prevData) =>
prevData?.map((p) => {
if (p.id === data.id) return { ...p, ...payload };

return p;
}),
false
);

await estimatesService
.patchEstimate(workspaceSlug as string, projectId as string, data?.id as string, payload)
.then(() => {
handleClose();
})
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Error: Estimate could not be updated",
});
});
handleClose();
};

useEffect(() => {
if (!data && isCreate) return;
reset({
...defaultValues,
...data,
});
}, [data, reset, isCreate]);

return (
<>
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-white px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form
onSubmit={data ? handleSubmit(updateEstimate) : handleSubmit(createEstimate)}
>
<div className="space-y-3">
<div className="text-2xl font-medium">Create Estimate</div>
<div>
<Input
id="name"
name="name"
type="name"
placeholder="Title"
autoComplete="off"
mode="transparent"
className="resize-none text-xl"
error={errors.name}
register={register}
validations={{
required: "Title is required",
maxLength: {
value: 255,
message: "Title should be less than 255 characters",
},
}}
/>
</div>
<div>
<TextArea
id="description"
name="description"
placeholder="Description"
className="h-32 resize-none text-sm"
mode="transparent"
error={errors.description}
register={register}
/>
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
{data
? isSubmitting
? "Updating Estimate..."
: "Update Estimate"
: isSubmitting
? "Creating Estimate..."
: "Create Estimate"}
</PrimaryButton>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</>
);
};
Loading

0 comments on commit 95fe4a3

Please sign in to comment.