diff --git a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx index def2cde8..16885062 100644 --- a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx +++ b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useFormik } from 'formik'; import { toast } from 'react-hot-toast'; @@ -8,16 +8,23 @@ import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Button from '@/components/Button'; -import TextInput from '@/components/input/TextInput'; import SelectInput, { OptionType, - // useSelect, + useSelect, } from '@/components/input/SelectInput'; import TextArea from '@/components/input/TextArea'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import DateInput from '@/components/input/DateInput'; +import NumberInput from '@/components/input/NumberInput'; +import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; +import ApprovalSteps, { + formatGroupedApprovalsToApprovalSteps, +} from '@/components/pages/ApprovalSteps'; import { + getFilledTransferToLayingFormInitialValues, + getTransferToLayingFormInitialValues, TransferToLayingFormSchema, TransferToLayingFormValues, UpdateTransferToLayingFormSchema, @@ -31,6 +38,8 @@ import { import { cn } from '@/lib/helper'; import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying'; +import { ProjectFlock } from '@/types/api/production/project-flock'; +import { TRANSFER_TO_LAYING_APPROVAL_LINE } from '@/config/approval-line'; interface TransferToLayingFormProps { type?: 'add' | 'edit' | 'detail'; @@ -55,11 +64,23 @@ const TransferToLayingForm = ({ const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); + const { data: approvalHistory, isLoading: isLoadingApprovalHistory } = useSWR( + type === 'detail' && initialValues ? [String(initialValues.id)] : null, + ([id]: string[]) => TransferToLayingApi.getApprovalHistory(Number(id)) + ); + const createTransferToLayingHandler = useCallback( async (payload: CreateTransferToLayingPayload) => { - console.log('Create transfer to laying:', { payload }); + const createTransferToLayingRes = + await TransferToLayingApi.create(payload); - toast.success('Berhasil menambahkan data transfer ke laying!'); + if (isResponseError(createTransferToLayingRes)) { + setFormErrorMessage(createTransferToLayingRes.message); + return; + } + + toast.success(createTransferToLayingRes?.message as string); + router.push('/production/transfer-to-laying'); }, [router] ); @@ -69,46 +90,30 @@ const TransferToLayingForm = ({ transferToLayingId: number, payload: UpdateTransferToLayingPayload ) => { - console.log( - `Update transfer to laying with ID of ${transferToLayingId}:`, - { payload } + const updateKandangRes = await TransferToLayingApi.update( + transferToLayingId, + payload ); - toast.success('Berhasil mengubah data transfer ke laying!'); + if (updateKandangRes?.status === 'error') { + setFormErrorMessage(updateKandangRes.message); + return; + } + + toast.success(updateKandangRes?.message as string); + router.refresh(); + router.push('/production/transfer-to-laying'); }, [router] ); - const formikInitialValues = useMemo(() => { - return { - transfer_date: initialValues?.transfer_date ?? '', - flockSource: initialValues?.flock_source - ? { - value: initialValues?.flock_source.id, - label: initialValues?.flock_source.name, - } - : undefined, - flockDestination: initialValues?.flock_destination - ? { - value: initialValues?.flock_destination.id, - label: initialValues?.flock_destination.name, - } - : undefined, - totalQuantity: initialValues?.quantity ?? undefined, + // const formikInitialValues = useMemo(() => { + // return getTransferToLayingFormInitialValues(initialValues); + // }, [initialValues]); - kandangs: initialValues?.kandangs - ? initialValues.kandangs.map((kandang) => ({ - kandang: { - value: kandang.kandang.id, - label: kandang.kandang.name, - }, - quantity: kandang.quantity, - })) - : [], - - reason: initialValues?.reason ?? undefined, - }; - }, [initialValues]); + const [formikInitialValues, setFormikInitialValues] = useState( + getTransferToLayingFormInitialValues() + ); const formik = useFormik({ initialValues: formikInitialValues, @@ -117,23 +122,23 @@ const TransferToLayingForm = ({ ? UpdateTransferToLayingFormSchema : TransferToLayingFormSchema, onSubmit: async (values) => { - console.log({ values }); - setFormErrorMessage(''); const transferToLayingPayload: CreateTransferToLayingPayload = { transfer_date: values.transfer_date as string, - flock_source_id: values.flockSource?.value as number, - flock_destination_id: values.flockDestination?.value as number, + source_project_flock_id: values.flockSource?.value as number, + target_project_flock_id: values.flockDestination?.value as number, totalQuantity: values.totalQuantity as number, - kandangs: values.kandangs?.map((kandang) => ({ - kandang_id: kandang.kandang.value, - quantity: kandang.quantity, - })) as { - kandang_id: number; - quantity: number; - }[], + source_kandangs: values.flockSourceKandangs?.map((kandang) => ({ + project_flock_kandang_id: kandang.kandang.value, + quantity: parseFloat(kandang.quantity as string), + })) as CreateTransferToLayingPayload['source_kandangs'], + + target_kandangs: values.flockDestinationKandangs?.map((kandang) => ({ + project_flock_kandang_id: kandang.kandang.value, + quantity: parseFloat(kandang.quantity as string), + })) as CreateTransferToLayingPayload['target_kandangs'], reason: values.reason as string, }; @@ -154,7 +159,11 @@ const TransferToLayingForm = ({ }); const { setValues: formikSetValues, values: formikValues } = formik; - const { kandangs: kandangsValue } = formikValues; + const { + flockSourceKandangs: flockSourceKandangsValue, + flockDestinationKandangs: flockDestinationKandangsValue, + totalQuantity, + } = formikValues; const deleteTransferToLayingClickHandler = () => { deleteModal.openModal(); @@ -172,24 +181,32 @@ const TransferToLayingForm = ({ const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - // TODO: delete data and integrate to real API - deleteModal.closeModal(); - toast.success('Berhasil menghapus data transfer ke laying!'); + try { + await TransferToLayingApi.delete(initialValues?.id as number); - setIsDeleteLoading(false); + toast.success('Berhasil menghapus data transfer ke laying!'); + router.push('/production/transfer-to-laying'); + } catch (error) { + toast.success('Gagal menghapus data transfer ke laying!'); + } finally { + deleteModal.closeModal(); + setIsDeleteLoading(false); + } }; - const confirmationModalApproveClickHandler = async () => { + const confirmationModalApproveClickHandler = async (notes: string) => { setIsApproveLoading(true); const approveResponse = await TransferToLayingApi.approve( - initialValues?.id as number + initialValues?.id as number, + notes ); if (isResponseSuccess(approveResponse)) { approveModal.closeModal(); toast.success('Berhasil approve data transfer ke laying!'); + router.push('/production/transfer-to-laying'); } else { approveModal.closeModal(); @@ -199,17 +216,19 @@ const TransferToLayingForm = ({ setIsApproveLoading(false); }; - const confirmationModalRejectClickHandler = async () => { + const confirmationModalRejectClickHandler = async (notes: string) => { setIsRejectLoading(true); const rejectResponse = await TransferToLayingApi.reject( - initialValues?.id as number + initialValues?.id as number, + notes ); if (isResponseSuccess(rejectResponse)) { rejectModal.closeModal(); toast.success('Berhasil reject data transfer ke laying!'); + router.push('/production/transfer-to-laying'); } else { rejectModal.closeModal(); @@ -219,49 +238,47 @@ const TransferToLayingForm = ({ setIsRejectLoading(false); }; - const isRepeaterInputError = ( - column: keyof TransferToLayingFormValues['kandangs'][0], + // flock source + const isFlockSourceKandangsRepeaterInputError = ( + column: keyof TransferToLayingFormValues['flockSourceKandangs'][0], idx: number ) => { return ( - formik.touched.kandangs?.[idx]?.[column] && + formik.touched.flockSourceKandangs?.[idx]?.[column] && Boolean( - formik.errors.kandangs?.[idx] instanceof Object && - formik.errors.kandangs?.[idx]?.[column] + formik.errors.flockSourceKandangs?.[idx] instanceof Object && + formik.errors.flockSourceKandangs?.[idx]?.[column] ) ); }; - const repeaterInputErrorMessage = ( - column: keyof TransferToLayingFormValues['kandangs'][0], + const flockSourceKandangsRepeaterInputErrorMessage = ( + column: keyof TransferToLayingFormValues['flockSourceKandangs'][0], idx: number ) => { - return (formik.errors.kandangs?.[idx] as Record)?.[column]; + return ( + formik.errors.flockSourceKandangs?.[idx] as Record + )?.[column]; }; - // TODO: remove dummy data and use real data - // Flock Source - // const { - // inputValue: flockSourceInputValue, - // setInputValue: setFlockSourceInputValue, - // options: flockSourceOptions, - // isLoadingOptions: isLoadingFlockSourceOptions, - // } = useSelect('/transfer-to-laying/production/get-flock-source', 'id', 'name'); - - // TODO: remove this dummy data - const { data: flockSources, isLoading: isLoadingFlockSourceOptions } = useSWR( - 'test', - () => TransferToLayingApi.getFlockSource() + const { + setInputValue: setFlockSourceInputValue, + options: flockSourceOptions, + isLoadingOptions: isLoadingFlockSourceOptions, + rawData: flockSources, + } = useSelect( + '/production/project-flocks', + 'id', + 'flock_name', + 'search', + { + category: 'GROWING', + } ); - const flockSourceOptions = isResponseSuccess(flockSources) - ? flockSources?.data.map((flockSource) => ({ - value: flockSource.id, - label: flockSource.name, - })) - : []; - - const flockSourceChangeHandler = (val: OptionType | OptionType[] | null) => { + const flockSourceChangeHandler = async ( + val: OptionType | OptionType[] | null + ) => { // Get flock source data for total quantity and kandang const flockSource = isResponseSuccess(flockSources) && val !== null @@ -272,21 +289,38 @@ const TransferToLayingForm = ({ // Set total quantity and kandangs if (flockSource) { + const mappedFlockKandangsAvailableQty = + await TransferToLayingApi.getMappedFlockKandangsAvailability( + flockSource.id + ); + const formattedKandangs = flockSource.kandangs.map((item) => ({ kandang: { - value: item.kandang.id, - label: item.kandang.name, + value: item.project_flock_kandang_id, + label: item.name, }, quantity: '', - maxQuantity: item.quantity, + maxQuantity: + (mappedFlockKandangsAvailableQty && + mappedFlockKandangsAvailableQty[item.project_flock_kandang_id] + .available_qty) ?? + 0, })); - formik.setFieldValue('totalQuantity', flockSource.totalQuantity); - formik.setFieldValue('maxTotalQuantity', flockSource.totalQuantity); - formik.setFieldValue('kandangs', formattedKandangs); + let maxTotalQuantity = 0; + // flockSource.kandangs.forEach((item) => { + // maxTotalQuantity += item.capacity; + // }); + formattedKandangs.forEach((item) => { + maxTotalQuantity += item.maxQuantity; + }); + + formik.setFieldValue('totalQuantity', ''); + formik.setFieldValue('maxTotalQuantity', maxTotalQuantity); + formik.setFieldValue('flockSourceKandangs', formattedKandangs); } else { formik.setFieldValue('totalQuantity', undefined); - formik.setFieldValue('kandangs', undefined); + formik.setFieldValue('flockSourceKandangs', undefined); formik.setFieldValue('reason', ''); } @@ -294,52 +328,137 @@ const TransferToLayingForm = ({ formik.setFieldValue('flockSource', val); }; - // TODO: remove dummy data and use real data - // Flock Destination - // const { - // inputValue: flockDestinationInputValue, - // setInputValue: setFlockDestinationInputValue, - // options: flockDestinationOptions, - // isLoadingOptions: isLoadingFlockDestinationOptions, - // } = useSelect('/transfer-to-laying/production/get-flock-destination', 'id', 'name'); + // flock destination + const isFlockDestinationKandangsRepeaterInputError = ( + column: keyof TransferToLayingFormValues['flockDestinationKandangs'][0], + idx: number + ) => { + return ( + formik.touched.flockDestinationKandangs?.[idx]?.[column] && + Boolean( + formik.errors.flockDestinationKandangs?.[idx] instanceof Object && + formik.errors.flockDestinationKandangs?.[idx]?.[column] + ) + ); + }; + + const flockDestinationKandangsRepeaterInputErrorMessage = ( + column: keyof TransferToLayingFormValues['flockDestinationKandangs'][0], + idx: number + ) => { + return ( + formik.errors.flockDestinationKandangs?.[idx] as Record + )?.[column]; + }; - // TODO: remove this dummy data const { - data: flockDestinations, - isLoading: isLoadingFlockDestinationOptions, - } = useSWR('test', () => TransferToLayingApi.getFlockSource()); - - const flockDestinationOptions = isResponseSuccess(flockDestinations) - ? flockDestinations?.data.map((flockDestination) => ({ - value: flockDestination.id, - label: flockDestination.name, - })) - : []; + setInputValue: setFlockDestinationInputValue, + options: flockDestinationOptions, + isLoadingOptions: isLoadingFlockDestinationOptions, + rawData: flockDestinations, + } = useSelect( + '/production/project-flocks', + 'id', + 'flock_name', + 'search', + { + category: 'LAYING', + } + ); const flockDestinationChangeHandler = ( val: OptionType | OptionType[] | null ) => { + // Get flock destination data for total quantity and kandang + const flockDestination = + isResponseSuccess(flockDestinations) && val !== null + ? flockDestinations.data.find( + (item) => item.id === (val as OptionType).value + ) + : undefined; + + // Set total quantity and kandangs + if (flockDestination) { + const formattedKandangs = flockDestination.kandangs.map((item) => ({ + kandang: { + value: item.project_flock_kandang_id, + label: item.name, + }, + quantity: '', + + // TODO: integrate this later to real kandang capacity API + // maxQuantity: item.capacity ?? 0, + maxQuantity: item.capacity ?? Infinity, + })); + + formik.setFieldValue('flockDestinationKandangs', formattedKandangs); + } + formik.setFieldTouched('flockDestination', true); formik.setFieldValue('flockDestination', val); }; + const isShowApproveRejectButton = + initialValues && + initialValues?.approval?.step_number === 1 && + initialValues?.approval.action !== 'REJECTED'; + + const isShowDeleteButton = + initialValues && + initialValues?.approval.action !== 'REJECTED' && + initialValues?.approval.action !== 'APPROVED'; + + const isShowEditButton = isShowDeleteButton; + + useEffect(() => { + const getFilledInitialValues = async () => { + if (initialValues) { + const filledInitialValues = + await getFilledTransferToLayingFormInitialValues(initialValues); + + setFormikInitialValues(filledInitialValues); + } + }; + + getFilledInitialValues(); + }, [initialValues, setFormikInitialValues]); + useEffect(() => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); useEffect(() => { // calculate total quantity if kandangs quantity change - if (kandangsValue && kandangsValue.length > 0) { + if (flockSourceKandangsValue && flockSourceKandangsValue.length > 0) { let newTotalQuantity = 0; - kandangsValue.forEach((item) => { - newTotalQuantity += item.quantity as number; + flockSourceKandangsValue.forEach((item) => { + newTotalQuantity += parseFloat(item.quantity as string); }); formik.setFieldValue('totalQuantity', newTotalQuantity); formik.validateField('totalQuantity'); } - }, [formikSetValues, kandangsValue]); + }, [formikSetValues, flockSourceKandangsValue]); + + useEffect(() => { + // calculate total quantity if kandangs quantity change + if ( + flockDestinationKandangsValue && + flockDestinationKandangsValue.length > 0 + ) { + let destinationKandangsTotalQuantity = 0; + + flockDestinationKandangsValue.forEach((item) => { + destinationKandangsTotalQuantity += parseFloat(item.quantity as string); + }); + + if ( + destinationKandangsTotalQuantity > parseFloat(String(totalQuantity)) + ) { + } + } + }, [formikSetValues, flockDestinationKandangsValue]); return ( <> @@ -361,30 +480,56 @@ const TransferToLayingForm = ({ -
+ {type === 'detail' && + initialValues && + !isLoadingApprovalHistory && + isResponseSuccess(approvalHistory) && ( +
+ +
+ )} + +
{type === 'detail' && ( <> - + {isShowApproveRejectButton && ( +
+ {/* TODO: apply RBAC */} + - + +
+ )} )}
@@ -395,13 +540,12 @@ const TransferToLayingForm = ({ className='w-full flex flex-col gap-6' >
-
- -
+
- + - {(!formik.values.kandangs || - formik.values.kandangs.length === 0) && ( + {(!formik.values.flockSourceKandangs || + formik.values.flockSourceKandangs.length === 0) && ( )} - {formik.values.kandangs && - formik.values.kandangs.map((kandang, idx) => ( + {formik.values.flockSourceKandangs && + formik.values.flockSourceKandangs.map((kandang, idx) => (
KandangKandang Flock Asal Kuantitas

@@ -496,8 +639,8 @@ const TransferToLayingForm = ({

-
+ +
+ + + + + + + + + + {(!formik.values.flockDestinationKandangs || + formik.values.flockDestinationKandangs.length === 0) && ( + + + + )} + + {formik.values.flockDestinationKandangs && + formik.values.flockDestinationKandangs.map( + (kandang, idx) => ( + + + + + + ) + )} + +
Kandang Flock TujuanKuantitas
+

+ Pilih flock tujuan terlebih dahulu! +

+
+ + + +
+