From a83452a0e4aa9b043023ba1cf0d52420e21b1700 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 9 Oct 2025 12:27:59 +0700 Subject: [PATCH] feat(FE-33): create suppliers table and forms --- .gitignore | 3 + src/app/master-data/customer/add/page.tsx | 4 +- src/app/master-data/customer/page.tsx | 6 +- src/app/master-data/supplier/add/page.tsx | 11 + .../master-data/supplier/detail/edit/page.tsx | 49 ++ src/app/master-data/supplier/detail/page.tsx | 49 ++ src/app/master-data/supplier/page.tsx | 11 + src/components/input/TagInput.tsx | 169 +++++++ .../master-data/customer/CustomersTable.tsx | 4 +- .../customer/form/CustomerForm.schema.ts | 10 +- .../customer/form/CustomerForm.tsx | 12 +- .../master-data/supplier/SupplierTable.tsx | 303 +++++++++++ .../supplier/form/SupplierForm.schema.ts | 38 ++ .../supplier/form/SupplierForm.tsx | 471 ++++++++++++++++++ src/config/constant.ts | 13 +- src/services/api/master-data.ts | 13 +- src/types/api/master-data/supplier.d.ts | 53 ++ 17 files changed, 1194 insertions(+), 25 deletions(-) create mode 100644 src/app/master-data/supplier/add/page.tsx create mode 100644 src/app/master-data/supplier/detail/edit/page.tsx create mode 100644 src/app/master-data/supplier/detail/page.tsx create mode 100644 src/app/master-data/supplier/page.tsx create mode 100644 src/components/input/TagInput.tsx create mode 100644 src/components/pages/master-data/supplier/SupplierTable.tsx create mode 100644 src/components/pages/master-data/supplier/form/SupplierForm.schema.ts create mode 100644 src/components/pages/master-data/supplier/form/SupplierForm.tsx create mode 100644 src/types/api/master-data/supplier.d.ts diff --git a/.gitignore b/.gitignore index 5ef6a520..beb014a7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# prettier +.prettierrc \ No newline at end of file diff --git a/src/app/master-data/customer/add/page.tsx b/src/app/master-data/customer/add/page.tsx index f303bb32..a1096f02 100644 --- a/src/app/master-data/customer/add/page.tsx +++ b/src/app/master-data/customer/add/page.tsx @@ -1,6 +1,6 @@ import CustomerForm from "@/components/pages/master-data/customer/form/CustomerForm"; -const AddNonstock = () => { +const AddCustomer = () => { return (
@@ -8,4 +8,4 @@ const AddNonstock = () => { ); } -export default AddNonstock; \ No newline at end of file +export default AddCustomer; \ No newline at end of file diff --git a/src/app/master-data/customer/page.tsx b/src/app/master-data/customer/page.tsx index 56281702..b80401f1 100644 --- a/src/app/master-data/customer/page.tsx +++ b/src/app/master-data/customer/page.tsx @@ -1,11 +1,11 @@ import CustomersTable from "@/components/pages/master-data/customer/CustomersTable"; -const Nonstock = () => { +const Customer = () => { return ( -
+
) }; -export default Nonstock; \ No newline at end of file +export default Customer; \ No newline at end of file diff --git a/src/app/master-data/supplier/add/page.tsx b/src/app/master-data/supplier/add/page.tsx new file mode 100644 index 00000000..8a95c3c6 --- /dev/null +++ b/src/app/master-data/supplier/add/page.tsx @@ -0,0 +1,11 @@ +import SupplierForm from '@/components/pages/master-data/supplier/form/SupplierForm'; + +const AddSupplier = () => { + return ( +
+ +
+ ); +}; + +export default AddSupplier; \ No newline at end of file diff --git a/src/app/master-data/supplier/detail/edit/page.tsx b/src/app/master-data/supplier/detail/edit/page.tsx new file mode 100644 index 00000000..103db73d --- /dev/null +++ b/src/app/master-data/supplier/detail/edit/page.tsx @@ -0,0 +1,49 @@ +'use client'; + +import SupplierForm from '@/components/pages/master-data/supplier/form/SupplierForm'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { SupplierApi } from '@/services/api/master-data'; +import { useSearchParams, useRouter } from 'next/navigation'; +import useSWR from 'swr'; + +const SupplierEdit = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get Query Params + const supplierId = searchParams.get('supplierId'); + + // Fetch Data + const { data: supplier, isLoading: isLoadingSupplier } = useSWR( + supplierId, + (id: number) => SupplierApi.getSingle(id) + ); + + if (!supplierId) { + router.back(); + + return ( +
+ +
+ ); + } + + if (!isLoadingSupplier && (!supplier || isResponseError(supplier))) { + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingSupplier && ( + + )} + {!isLoadingSupplier && isResponseSuccess(supplier) && ( + + )} +
+ ); +}; + +export default SupplierEdit; diff --git a/src/app/master-data/supplier/detail/page.tsx b/src/app/master-data/supplier/detail/page.tsx new file mode 100644 index 00000000..433fa043 --- /dev/null +++ b/src/app/master-data/supplier/detail/page.tsx @@ -0,0 +1,49 @@ +'use client'; + +import SupplierForm from '@/components/pages/master-data/supplier/form/SupplierForm'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { SupplierApi } from '@/services/api/master-data'; +import { useSearchParams, useRouter } from 'next/navigation'; +import useSWR from 'swr'; + +const SupplierDetail = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get Query Params + const supplierId = searchParams.get('supplierId'); + + // Fetch Data + const { data: supplier, isLoading: isLoadingSupplier } = useSWR( + supplierId, + (id: number) => SupplierApi.getSingle(id) + ); + + if (!supplierId) { + router.back(); + + return ( +
+ +
+ ); + } + + if (!isLoadingSupplier && (!supplier || isResponseError(supplier))) { + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingSupplier && ( + + )} + {!isLoadingSupplier && isResponseSuccess(supplier) && ( + + )} +
+ ); +}; + +export default SupplierDetail; \ No newline at end of file diff --git a/src/app/master-data/supplier/page.tsx b/src/app/master-data/supplier/page.tsx new file mode 100644 index 00000000..1f54bd0d --- /dev/null +++ b/src/app/master-data/supplier/page.tsx @@ -0,0 +1,11 @@ +import SuppliersTable from "@/components/pages/master-data/supplier/SupplierTable"; + +const Supplier = () => { + return ( +
+ +
+ ); +}; + +export default Supplier; diff --git a/src/components/input/TagInput.tsx b/src/components/input/TagInput.tsx new file mode 100644 index 00000000..a14b2f63 --- /dev/null +++ b/src/components/input/TagInput.tsx @@ -0,0 +1,169 @@ +'use client'; + +import React, { useState, KeyboardEvent, ChangeEvent, useEffect } from 'react'; +import { cn } from '@/lib/helper'; + +export interface TagInputProps { + label?: string; + bottomLabel?: string; + name: string; + value?: string; + placeholder?: string; + className?: { + wrapper?: string; + label?: string; + inputWrapper?: string; + input?: string; + }; + isError?: boolean; + isValid?: boolean; + disabled?: boolean; + readOnly?: boolean; + required?: boolean; + isLoading?: boolean; + errorMessage?: string; + onChange?: (value: string) => void; +} + +const TagInput: React.FC = ({ + label, + bottomLabel, + name, + value = '', + placeholder, + className, + isError, + isValid, + errorMessage, + disabled = false, + readOnly = false, + required = false, + onChange, +}) => { + const [tags, setTags] = useState(value ? value.split(',') : []); + const [inputValue, setInputValue] = useState(''); + + useEffect(() => { + if (value !== undefined && value !== tags.join(',')) { + setTags(value ? value.split(',') : []); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ',') { + e.preventDefault(); + const newTag = inputValue.trim(); + if (newTag && !tags.includes(newTag)) { + const updatedTags = [...tags, newTag]; + setTags(updatedTags); + onChange?.(updatedTags.join(',')); + } + setInputValue(''); + } + }; + + const handleRemoveTag = (tagToRemove: string) => { + const updatedTags = tags.filter((t) => t !== tagToRemove); + setTags(updatedTags); + onChange?.(updatedTags.join(',')); + }; + + const handleInputChange = (e: ChangeEvent) => { + setInputValue(e.target.value); + }; + + return ( +
+ {/* Label */} + {label && ( + + )} + + {/* Input wrapper */} +
{ + // Fokuskan input saat area diklik + const inputEl = document.getElementById(name); + inputEl?.focus(); + }} + > + {tags.map((tag) => ( +
+ {tag} + {!readOnly && ( + + )} +
+ ))} + + {!readOnly && ( + + )} +
+ + {/* Bottom label or error message */} + {!isError && bottomLabel && ( +

{bottomLabel}

+ )} + {isError &&

{errorMessage}

} +
+ ); +}; + +export default TagInput; diff --git a/src/components/pages/master-data/customer/CustomersTable.tsx b/src/components/pages/master-data/customer/CustomersTable.tsx index e1811368..d3fde60b 100644 --- a/src/components/pages/master-data/customer/CustomersTable.tsx +++ b/src/components/pages/master-data/customer/CustomersTable.tsx @@ -18,8 +18,6 @@ import { Icon } from '@iconify/react'; import { CellContext, ColumnDef, - ColumnSort, - SortingState, } from '@tanstack/react-table'; import { useState } from 'react'; import toast from 'react-hot-toast'; @@ -272,7 +270,7 @@ const CustomersTable = () => { { - if (!type) return CUSTOMER_TYPE_OPTIONS[0]; + if (!type) return TYPE_OPTIONS[0]; return typeof type === 'string' ? { value: type, label: type } : type; }; @@ -151,6 +151,7 @@ const CustomerForm = ({ const formik = useFormik({ initialValues: formikInitialValues, enableReinitialize: true, + validationSchema: formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema, onSubmit: async (values) => { // reset error message setCustomerFormErrorMessage(''); @@ -213,7 +214,6 @@ const CustomerForm = ({ > {/* Fields Form */}
- {formik.values.picId} item.value === formik.values.type.value + (item) => item.value === formik.values.type?.value ) ?? undefined } onChange={typeChangeHandler} diff --git a/src/components/pages/master-data/supplier/SupplierTable.tsx b/src/components/pages/master-data/supplier/SupplierTable.tsx new file mode 100644 index 00000000..672f70a6 --- /dev/null +++ b/src/components/pages/master-data/supplier/SupplierTable.tsx @@ -0,0 +1,303 @@ +'use client'; + +import Button from '@/components/Button'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import { useModal } from '@/components/Modal'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import Table from '@/components/Table'; +import RowCollapseOptions from '@/components/table/RowCollapseOptions'; +import RowDropdownOptions from '@/components/table/RowDropdownOptions'; +import { ROWS_OPTIONS } from '@/config/constant'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { cn } from '@/lib/helper'; +import { SupplierApi } from '@/services/api/master-data'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { Supplier } from '@/types/api/master-data/supplier'; +import { Icon } from '@iconify/react'; +import { CellContext, ColumnDef } from '@tanstack/react-table'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import useSWR from 'swr'; + +const RowOptions = ({ + type = 'dropdown', + props, + deleteClickHandler, +}: { + type: 'dropdown' | 'collapse'; + props: CellContext; + deleteClickHandler: () => void; +}) => { + return ( +
+ + + +
+ ); +}; + +const SuppliersTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { search: '', nameSort: '' }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + }, + }); + + // Fetch Data + const { + data: suppliers, + isLoading, + mutate: refreshSuppliers, + } = useSWR( + `${SupplierApi.basePath}${getTableFilterQueryString()}`, + SupplierApi.getAllFetcher + ); + + // State + const deleteModal = useModal(); + const [selectedSupplier, setSelectedSupplier] = useState< + Supplier | undefined + >(undefined); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // Columns Definition + const suppliersColumns: ColumnDef[] = [ + { + header: '#', + cell: (props) => + tableFilterState.pageSize * (tableFilterState.page - 1) + + props.row.index + + 1, + }, + { + accessorKey: 'name', + header: 'Nama', + }, + { + accessorKey: 'alias', + header: 'Alias', + }, + { + accessorKey: 'pic', + header: 'Nama PIC', + }, + { + accessorKey: 'category', + header: 'Kategori', + }, + { + accessorKey: 'type', + header: 'Tipe', + }, + { + accessorKey: 'phone', + header: 'No. Telp', + }, + { + accessorKey: 'email', + header: 'Email', + }, + { + accessorKey: 'address', + header: 'Alamat', + }, + { + header: 'Aksi', + cell: (props) => { + const currentPageSize = props.table.getPaginationRowModel().rows.length; + const currentPageRows = props.table.getPaginationRowModel().flatRows; + const currentRowRelativeIndex = + currentPageRows.findIndex((r) => r.id === props.row.id) + 1; + + const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; + + const deleteClickHandler = () => { + setSelectedSupplier(props.row.original); + deleteModal.openModal(); + }; + + return ( + <> + {currentPageSize > 2 && ( + + + + )} + + {currentPageSize <= 2 && ( + + + + )} + + ); + }, + }, + ]; + + // Handler + const confirmationModalDeleteClickHandler = async () => { + setIsDeleteLoading(true); + + await SupplierApi.delete(selectedSupplier?.id as number); + refreshSuppliers(); + + deleteModal.closeModal(); + toast.success('Successfully delete Supplier!'); + setIsDeleteLoading(false); + }; + const searchChangeHandler = (e: React.ChangeEvent) => { + updateFilter('search', e.target.value); + }; + const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { + const newVal = val as OptionType; + setPageSize(newVal.value as number); + }; + + return ( + <> +
+
+
+
+ +
+ + +
+ +
+ +
+
+ + + data={isResponseSuccess(suppliers) ? suppliers?.data : []} + columns={suppliersColumns} + pageSize={tableFilterState.pageSize} + page={isResponseSuccess(suppliers) ? suppliers?.meta?.page : 0} + totalItems={ + isResponseSuccess(suppliers) ? suppliers?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoading} + className={{ + containerClassName: cn({ + 'mb-20': + isResponseSuccess(suppliers) && suppliers?.data?.length === 0, + }), + tableWrapperClassName: 'overflow-x-auto min-h-full!', + tableClassName: 'font-inter w-full table-auto min-h-full!', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', + bodyRowClassName: 'border-b border-b-gray-200', + bodyColumnClassName: + 'px-6 py-3 last:flex last:flex-row last:justify-end', + }} + /> +
+ + + ); +}; + +export default SuppliersTable; diff --git a/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts b/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts new file mode 100644 index 00000000..69f127a3 --- /dev/null +++ b/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts @@ -0,0 +1,38 @@ +import * as Yup from 'yup'; + +export const SupplierFormSchema = Yup.object({ + name: Yup.string().required('Nama wajib diisi!'), + alias: Yup.string().required('Alias wajib diisi!'), + pic: Yup.string().required('PIC wajib diisi!'), + type: Yup.object({ + value: Yup.string().required(), + label: Yup.string().required(), + }) + .required('Tipe wajib diisi!'), + category: Yup.object({ + value: Yup.string().required(), + label: Yup.string().required(), + }) + .required('Tipe wajib diisi!'), + hatchery: Yup.string().required('Hatchery wajib diisi!'), + phone: Yup.string() + .matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!') + .min(10, 'Nomor telepon minimal 10 digit!') + .max(12, 'Nomor telepon maksimal 12 digit!') + .required('Nomor telepon wajib diisi!'), + email: Yup.string() + .email('Format email tidak valid!') + .required('Email wajib diisi!'), + address: Yup.string().required('Alamat wajib diisi!'), + npwp: Yup.string() + .matches(/^[0-9]+$/, 'Nomor NPWP hanya boleh berisi angka!') + .required('Nomor NPWP wajib diisi!'), + account_number: Yup.string() + .matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!') + .required('Nomor rekening wajib diisi!'), + due_date: Yup.number().min(1, 'Tanggal jatuh tempo wajib diisi!').required('Tanggal jatuh tempo wajib diisi!'), +}); + +export const UpdateSupplierFormSchema = SupplierFormSchema; + +export type SupplierFormValues = Yup.InferType; \ No newline at end of file diff --git a/src/components/pages/master-data/supplier/form/SupplierForm.tsx b/src/components/pages/master-data/supplier/form/SupplierForm.tsx new file mode 100644 index 00000000..9e0ac83a --- /dev/null +++ b/src/components/pages/master-data/supplier/form/SupplierForm.tsx @@ -0,0 +1,471 @@ +'use client'; + +import { useModal } from '@/components/Modal'; +import { CATEGORY_OPTIONS, TYPE_OPTIONS } from '@/config/constant'; +import { isResponseError } from '@/lib/api-helper'; +import { SupplierApi } from '@/services/api/master-data'; +import { + CreateSupplierPayload, + Supplier, +} from '@/types/api/master-data/supplier'; +import { useRouter } from 'next/navigation'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import toast from 'react-hot-toast'; +import { SupplierFormSchema, SupplierFormValues, UpdateSupplierFormSchema } from './SupplierForm.schema'; +import { useFormik } from 'formik'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import { Icon } from '@iconify/react'; +import Button from '@/components/Button'; +import TextInput from '@/components/input/TextInput'; +import TagInput from '@/components/input/TagInput'; +import TextArea from '@/components/input/TextArea'; +import { cn } from '@/lib/helper'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; + +interface SupplierCustomProps { + formType?: 'add' | 'edit' | 'detail'; + initialValues?: Supplier; +} + +const SupplierForm = ({ + formType = 'add', + initialValues, +}: SupplierCustomProps) => { + // Setup Kebutuhan Form + const router = useRouter(); + const deleteModal = useModal(); + + // Setup State + const [supplierFormErrorMessage, setSupplierFormErrorMessage] = useState(''); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [typeSelectInputValue, setTypeSelectInputValue] = useState(''); + const [categorySelectInputValue, setCategorySelectInputValue] = useState(''); + const [hatcheryTagInputValue, setHatcheryTagInputValue] = useState(''); + + // -- Options data mapping + const typeOptions = TYPE_OPTIONS; + const categoryOptions = CATEGORY_OPTIONS; + + // Handler Event + const createSupplierHandler = useCallback( + async (payload: CreateSupplierPayload) => { + const createSupplierRes = await SupplierApi.create(payload); + + if (isResponseError(createSupplierRes)) { + setSupplierFormErrorMessage(createSupplierRes.message); + return; + } + + toast.success(createSupplierRes?.message as string); + router.push('/master-data/supplier'); + }, + [router] + ); + + const updateSupplierHandler = useCallback( + async (supplierId: number, payload: CreateSupplierPayload) => { + const updateSupplierRes = await SupplierApi.update(supplierId, payload); + + if (isResponseError(updateSupplierRes)) { + setSupplierFormErrorMessage(updateSupplierRes.message); + return; + } + + toast.success(updateSupplierRes?.message as string); + router.push('/master-data/supplier'); + }, + [router] + ); + + const deleteSupplierHandler = () => { + deleteModal.openModal(); + }; + + const confirmationModalDeleteclickHandler = async () => { + setIsDeleteLoading(true); + + await SupplierApi.delete(initialValues?.id as number); + + deleteModal.closeModal(); + setIsDeleteLoading(false); + router.push('/master-data/supplier'); + }; + + // Utils Functions + const normalizeOptionValue = ( + type?: string | { value: string; label: string }, + options?: OptionType[] + ): { value: string; label: string } => { + if (!type && !options) return { value: '', label: '' }; + if (!type && options && options.length > 0) + return options[0] as { value: string; label: string }; + if (typeof type === 'string') return { value: type, label: type }; + return type ?? { value: '', label: '' }; + }; + + // Memo + console.log('Memo'); + console.log(initialValues); + const formikInitialValues = useMemo(() => { + return { + name: initialValues?.name ?? '', + alias: initialValues?.alias ?? '', + pic: initialValues?.pic ?? '', + type: normalizeOptionValue(initialValues?.type, typeOptions), + category: normalizeOptionValue(initialValues?.category, categoryOptions), + hatchery: initialValues?.hatchery ?? '', + phone: initialValues?.phone ?? '', + email: initialValues?.email ?? '', + address: initialValues?.address ?? '', + npwp: initialValues?.npwp ?? '', + account_number: initialValues?.account_number ?? '', + due_date: initialValues?.due_date ?? 1, + }; + }, [initialValues]); + + // Formik + const formik = useFormik({ + initialValues: formikInitialValues, + enableReinitialize: true, + validationSchema: formType === 'edit' ? UpdateSupplierFormSchema : SupplierFormSchema, + onSubmit: async (values) => { + // reset error message + setSupplierFormErrorMessage(''); + + // create payload + const payload: CreateSupplierPayload = { + name: values.name, + alias: values.alias, + pic: values.pic, + type: values.type.value, + category: values.category.value, + hatchery: values.hatchery, + phone: values.phone, + email: values.email, + address: values.address, + npwp: values.npwp, + account_number: values.account_number, + due_date: parseInt(values.due_date.toString()), + }; + + // cek type form yang disubmit + switch (formType) { + case 'add': + await createSupplierHandler(payload); + break; + case 'edit': + await updateSupplierHandler(initialValues?.id as number, payload); + break; + default: + break; + } + }, + }); + + const { setValues: formikSetValues } = formik; + + // Initialize Formik + useEffect(() => { + formikSetValues(formikInitialValues); + setHatcheryTagInputValue(formikInitialValues.hatchery); + }, [formikSetValues, formikInitialValues, hatcheryTagInputValue]); + + // Option Handler + const typeChangeHandler = (val: OptionType | OptionType[] | null) => { + formik.setFieldTouched('type', true); + formik.setFieldValue('type', val); + }; + const categoryChangeHandler = (val: OptionType | OptionType[] | null) => { + formik.setFieldTouched('category', true); + formik.setFieldValue('category', val); + }; + + // Render + return ( + <> +
+
+ + +

+ {formType === 'add' && 'Tambah Supplier'} + {formType === 'edit' && 'Ubah Supplier'} + {formType === 'detail' && 'Detail Supplier'} +

+
+ +
+ {/* Fields Form */} +
+ + + + item.value === formik.values.type?.value + ) ?? undefined + } + onChange={typeChangeHandler} + options={typeOptions} + onInputChange={setTypeSelectInputValue} + isError={formik.touched.type && Boolean(formik.errors.type)} + errorMessage={formik.errors.type as string} + isDisabled={formType === 'detail'} + isClearable + isSearchable={true} + /> + item.value === formik.values.category?.value + ) ?? undefined + } + onChange={categoryChangeHandler} + options={categoryOptions} + onInputChange={setCategorySelectInputValue} + isError={formik.touched.category && Boolean(formik.errors.category)} + errorMessage={formik.errors.category as string} + isDisabled={formType === 'detail'} + isClearable + isSearchable={true} + /> + formik.setFieldValue('hatchery', value)} + isError={formik.touched.hatchery && Boolean(formik.errors.hatchery)} + errorMessage={formik.errors.hatchery} + readOnly={formType === 'detail'} + /> + + +