From c153264c459e735e87a5fe2f5acc345af612d968 Mon Sep 17 00:00:00 2001 From: Tuval Simha Date: Sun, 18 Aug 2024 20:00:50 +0300 Subject: [PATCH] (5) Drop V2: Edit Operation Modal (#5316) --- .../laboratory/edit-operation-modal.tsx | 223 ++++++++++++------ .../stories/edit-operation-modal.stories.tsx | 84 +++++++ 2 files changed, 232 insertions(+), 75 deletions(-) create mode 100644 packages/web/app/src/stories/edit-operation-modal.stories.tsx diff --git a/packages/web/app/src/components/target/laboratory/edit-operation-modal.tsx b/packages/web/app/src/components/target/laboratory/edit-operation-modal.tsx index f9be3793f..cca66c6da 100644 --- a/packages/web/app/src/components/target/laboratory/edit-operation-modal.tsx +++ b/packages/web/app/src/components/target/laboratory/edit-operation-modal.tsx @@ -1,11 +1,27 @@ import { ReactElement, useMemo } from 'react'; -import { useFormik } from 'formik'; +import { useForm, UseFormReturn } from 'react-hook-form'; import { useMutation } from 'urql'; -import * as Yup from 'yup'; +import { z } from 'zod'; import { Button } from '@/components/ui/button'; -import { Heading } from '@/components/ui/heading'; -import { Input, Modal } from '@/components/v2'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { useToast } from '@/components/ui/use-toast'; import { graphql } from '@/gql'; +import { zodResolver } from '@hookform/resolvers/zod'; import { useCollections } from '../../../pages/target-laboratory'; const UpdateOperationNameMutation = graphql(` @@ -30,6 +46,24 @@ const UpdateOperationNameMutation = graphql(` } `); +const editOperationModalFormSchema = z.object({ + name: z + .string({ + required_error: 'Operation name is required', + }) + .min(3, { + message: 'Operation name must be at least 3 characters long', + }) + .max(50, { + message: 'Operation name must be less than 50 characters long', + }), + collectionId: z.string({ + required_error: 'Collection is required', + }), +}); + +export type EditOperationModalFormValues = z.infer; + export const EditOperationModal = (props: { operationId: string; close: () => void; @@ -37,6 +71,7 @@ export const EditOperationModal = (props: { projectId: string; targetId: string; }): ReactElement => { + const { toast } = useToast(); const [updateOperationNameState, mutate] = useMutation(UpdateOperationNameMutation); const { collections } = useCollections({ organizationId: props.organizationId, @@ -55,81 +90,119 @@ export const EditOperationModal = (props: { return [null, null] as const; }, [collections]); - const { handleSubmit, values, handleChange, handleBlur, errors, isValid, touched, isSubmitting } = - useFormik({ - initialValues: { - name: operation?.name ?? '', - collectionId: collection?.id ?? '', - }, - validationSchema: Yup.object().shape({ - name: Yup.string().min(3).required(), - collectionId: Yup.string().required('Collection is a required field'), - }), - async onSubmit(values) { - const response = await mutate({ - selector: { - target: props.targetId, - organization: props.organizationId, - project: props.projectId, - }, - input: { - collectionId: values.collectionId, - operationId: props.operationId, - name: values.name, - }, - }); - const error = response.error || response.data?.updateOperationInDocumentCollection?.error; + const form = useForm({ + mode: 'onChange', + resolver: zodResolver(editOperationModalFormSchema), + defaultValues: { + name: operation?.name || '', + collectionId: collection?.id || '', + }, + }); - if (!error) { - props.close(); - } + async function onSubmit(values: EditOperationModalFormValues) { + const response = await mutate({ + selector: { + target: props.targetId, + organization: props.organizationId, + project: props.projectId, + }, + input: { + collectionId: values.collectionId, + operationId: props.operationId, + name: values.name, }, }); + const error = response.error || response.data?.updateOperationInDocumentCollection?.error; + + if (!error) { + props.close(); + toast({ + title: 'Operation Updated', + description: 'Operation has been updated successfully', + variant: 'default', + }); + } else { + toast({ + title: 'Error', + description: error.message, + variant: 'destructive', + }); + } + } return ( - - {!updateOperationNameState.fetching && ( -
- Edit Operation - -
- - - {touched.name && errors.name && ( -
{errors.name}
- )} -
- - {updateOperationNameState.error && ( -
{updateOperationNameState.error.message}
- )} - -
- - -
-
- )} -
+ + ); +}; + +export const EditOperationModalContent = (props: { + fetching: boolean; + isOpen: boolean; + close: () => void; + form: UseFormReturn; + onSubmit: (values: EditOperationModalFormValues) => void; + opreationId?: string; +}): ReactElement => { + return ( + + + {!props.fetching && ( +
+ + + Edit Operation + +
+ { + return ( + + Operation Name + + + + + + ); + }} + /> +
+ + + + +
+ + )} +
+
); }; diff --git a/packages/web/app/src/stories/edit-operation-modal.stories.tsx b/packages/web/app/src/stories/edit-operation-modal.stories.tsx new file mode 100644 index 000000000..41074eb08 --- /dev/null +++ b/packages/web/app/src/stories/edit-operation-modal.stories.tsx @@ -0,0 +1,84 @@ +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { + EditOperationModalContent, + EditOperationModalFormValues, +} from '@/components/target/laboratory/edit-operation-modal'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Meta, StoryFn, StoryObj } from '@storybook/react'; + +const editOperationModalFormSchema = z.object({ + name: z + .string({ + required_error: 'Operation name is required', + }) + .min(2, { + message: 'Operation name must be at least 2 characters long', + }) + .max(50, { + message: 'Operation name must be less than 50 characters long', + }), + collectionId: z.string({ + required_error: 'Collection is required', + }), +}); + +const meta: Meta = { + title: 'Modals/Edit Operation Modal', + component: EditOperationModalContent, + argTypes: { + isOpen: { + control: { + type: 'boolean', + }, + }, + opreationId: { + control: { + type: 'text', + }, + }, + fetching: { + control: { + type: 'boolean', + }, + }, + close: { + action: 'close', + }, + onSubmit: { + action: 'onSubmit', + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const Template: StoryFn = args => { + const form = useForm({ + mode: 'onChange', + resolver: zodResolver(editOperationModalFormSchema), + defaultValues: { + name: args.opreationId, + collectionId: args.opreationId, + }, + }); + + useEffect(() => { + form.reset({ + name: args.opreationId, + collectionId: args.opreationId, + }); + }, [args.opreationId]); + + return ; +}; + +export const Default: Story = Template.bind({}); +Default.args = { + close: () => {}, + isOpen: true, + fetching: false, +};