mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
1087 lines
40 KiB
TypeScript
1087 lines
40 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
FormEventHandler,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from 'react';
|
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
import useSWR, { useSWRConfig } from 'swr';
|
|
import toast from 'react-hot-toast';
|
|
|
|
import { Icon } from '@iconify/react';
|
|
import Modal, { useModal } from '@/components/Modal';
|
|
import Button from '@/components/Button';
|
|
import DateInput from '@/components/input/DateInput';
|
|
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
|
import NumberInput from '@/components/input/NumberInput';
|
|
import TextArea from '@/components/input/TextArea';
|
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
|
import TransferToLayingConfirmationModal from '@/components/pages/production/transfer-to-laying/TransferToLayingConfirmationModal';
|
|
|
|
import { ProjectFlockApi } from '@/services/api/production';
|
|
import { getIn, useFormik } from 'formik';
|
|
import {
|
|
getFilledTransferToLayingFormInitialValues,
|
|
getTransferToLayingFormInitialValues,
|
|
TransferToLayingFormSchema,
|
|
TransferToLayingFormValues,
|
|
} from '@/components/pages/production/transfer-to-laying/form/TransferToLayingForm.schema';
|
|
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
|
import StatusBadge from '@/components/helper/StatusBadge';
|
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
|
import { ProjectFlock } from '@/types/api/production/project-flock';
|
|
import { cn, formatNumber } from '@/lib/helper';
|
|
import {
|
|
CreateTransferToLayingPayload,
|
|
UpdateTransferToLayingPayload,
|
|
} from '@/types/api/production/transfer-to-laying';
|
|
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
|
|
|
const TransferToLayingFormModal = () => {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
|
|
const modalAction = searchParams.get('action');
|
|
const transferToLayingId = searchParams.get('id');
|
|
|
|
const isModalActionForForm = modalAction === 'add' || modalAction === 'edit';
|
|
|
|
const { mutate } = useSWRConfig();
|
|
|
|
const refreshTransferToLayings = () => {
|
|
mutate(
|
|
(key) =>
|
|
typeof key === 'string' && key.includes(TransferToLayingApi.basePath)
|
|
);
|
|
};
|
|
|
|
const { data: transferToLaying } = useSWR(
|
|
isModalActionForForm && transferToLayingId
|
|
? ['detail-transfer-to-laying', transferToLayingId]
|
|
: undefined,
|
|
([, id]) => TransferToLayingApi.getSingle(Number(id))
|
|
);
|
|
|
|
/**
|
|
* Step 1: General Information
|
|
* Step 2: Select source and destination kandang
|
|
* Step 3: Enter transfered quantity
|
|
* Step 4: Submit
|
|
*/
|
|
const [step, setStep] = useState(1);
|
|
|
|
const formModal = useModal();
|
|
const successModal = useModal();
|
|
|
|
const [formikLastValues, setFormikLastValues] = useState<
|
|
TransferToLayingFormValues | undefined
|
|
>(undefined);
|
|
const [formErrorMessage, setFormErrorMessage] = useState<string | null>(null);
|
|
const [submittedActionType, setSubmittedActionType] = useState<
|
|
'add' | 'edit' | null
|
|
>(null);
|
|
|
|
// Flock Source
|
|
const {
|
|
setInputValue: setFlockSourceInputValue,
|
|
options: flockSourceOptions,
|
|
isLoadingOptions: isLoadingFlockSourceOptions,
|
|
loadMore: loadMoreFlockSource,
|
|
rawData: flockSourceRawData,
|
|
} = useSelect<ProjectFlock>(
|
|
ProjectFlockApi.basePath,
|
|
'id',
|
|
'flock_name',
|
|
'search',
|
|
{
|
|
category: 'GROWING',
|
|
transfer_context: 'transfer_to_laying',
|
|
is_approved: 'true',
|
|
}
|
|
);
|
|
|
|
// Flock Destination
|
|
const {
|
|
setInputValue: setFlockDestinationInputValue,
|
|
options: flockDestinationOptions,
|
|
isLoadingOptions: isLoadingFlockDestinationOptions,
|
|
loadMore: loadMoreFlockDestination,
|
|
rawData: flockDestinationRawData,
|
|
} = useSelect<ProjectFlock>(
|
|
ProjectFlockApi.basePath,
|
|
'id',
|
|
'flock_name',
|
|
'search',
|
|
{
|
|
category: 'LAYING',
|
|
is_approved: 'true',
|
|
}
|
|
);
|
|
|
|
const closeModalHandler = (shouldPushToRoute: boolean = true) => {
|
|
if (shouldPushToRoute) {
|
|
router.push('/production/transfer-to-laying');
|
|
}
|
|
|
|
formik.resetForm();
|
|
setStep(1);
|
|
setFormErrorMessage('');
|
|
formModal.closeModal();
|
|
};
|
|
|
|
const createTransferToLayingHandler = useCallback(
|
|
async (payload: CreateTransferToLayingPayload) => {
|
|
const createTransferToLayingRes =
|
|
await TransferToLayingApi.create(payload);
|
|
|
|
if (isResponseError(createTransferToLayingRes)) {
|
|
setFormErrorMessage(createTransferToLayingRes.message);
|
|
return;
|
|
}
|
|
|
|
refreshTransferToLayings();
|
|
toast.success(createTransferToLayingRes?.message as string);
|
|
router.push('/production/transfer-to-laying');
|
|
closeModalHandler(false);
|
|
successModal.openModal();
|
|
},
|
|
[router]
|
|
);
|
|
|
|
const updateTransferToLayingHandler = useCallback(
|
|
async (
|
|
transferToLayingId: number,
|
|
payload: UpdateTransferToLayingPayload
|
|
) => {
|
|
const updateKandangRes = await TransferToLayingApi.update(
|
|
transferToLayingId,
|
|
payload
|
|
);
|
|
|
|
if (updateKandangRes?.status === 'error') {
|
|
setFormErrorMessage(updateKandangRes.message);
|
|
return;
|
|
}
|
|
|
|
refreshTransferToLayings();
|
|
toast.success(updateKandangRes?.message as string);
|
|
router.push('/production/transfer-to-laying');
|
|
closeModalHandler(false);
|
|
successModal.openModal();
|
|
},
|
|
[router]
|
|
);
|
|
|
|
const [formikInitialValues] = useState(
|
|
getTransferToLayingFormInitialValues()
|
|
);
|
|
|
|
const formik = useFormik<TransferToLayingFormValues>({
|
|
initialValues: formikInitialValues,
|
|
validationSchema: TransferToLayingFormSchema,
|
|
onSubmit: async (values) => {
|
|
const transferToLayingPayload: CreateTransferToLayingPayload = {
|
|
transfer_date: values.transfer_date as string,
|
|
source_project_flock_id: values.flockSource?.value as number,
|
|
target_project_flock_id: values.flockDestination?.value as number,
|
|
totalQuantity: values.totalQuantity as 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,
|
|
};
|
|
|
|
setFormikLastValues(values);
|
|
setSubmittedActionType(modalAction as 'add' | 'edit');
|
|
|
|
switch (modalAction) {
|
|
case 'add':
|
|
await createTransferToLayingHandler(transferToLayingPayload);
|
|
break;
|
|
|
|
case 'edit':
|
|
await updateTransferToLayingHandler(
|
|
Number(transferToLayingId),
|
|
transferToLayingPayload
|
|
);
|
|
|
|
break;
|
|
}
|
|
},
|
|
});
|
|
|
|
const { flockSource: formikFlockSource } = formik.values;
|
|
|
|
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
|
|
|
const [selectedFlockSourceRawData, setSelectedFlockSourceRawData] = useState<
|
|
ProjectFlock | undefined
|
|
>(undefined);
|
|
|
|
const [maxSourceQuantity, setMaxSourceQuantity] = useState<number>(0);
|
|
|
|
const selectedFlockDestinationRawData = isResponseSuccess(
|
|
flockDestinationRawData
|
|
)
|
|
? flockDestinationRawData.data.find(
|
|
(item) => item.id === formik.values.flockDestination?.value
|
|
)
|
|
: undefined;
|
|
|
|
const { data: flockSourceKandangsAvailability } = useSWR(
|
|
formik.values.flockSource
|
|
? [
|
|
'transfer-to-laying',
|
|
'available-qty',
|
|
String(formik.values.flockSource.value),
|
|
]
|
|
: undefined,
|
|
([, , id]: string[]) =>
|
|
TransferToLayingApi.getMappedFlockKandangsAvailability(Number(id))
|
|
);
|
|
|
|
const mappedFlockSourceKandangsAvailability: {
|
|
kandang_name: string;
|
|
available_qty: number;
|
|
project_flock_kandang_id: number;
|
|
}[] = useMemo(() => {
|
|
if (!flockSourceKandangsAvailability || !selectedFlockSourceRawData)
|
|
return [];
|
|
|
|
return selectedFlockSourceRawData
|
|
? selectedFlockSourceRawData.kandangs.map((kandang) => {
|
|
const availability =
|
|
flockSourceKandangsAvailability[kandang.project_flock_kandang_id]
|
|
?.available_qty;
|
|
|
|
return {
|
|
kandang_name: kandang.name,
|
|
available_qty: availability,
|
|
project_flock_kandang_id: kandang.project_flock_kandang_id,
|
|
};
|
|
})
|
|
: [];
|
|
}, [flockSourceKandangsAvailability, selectedFlockSourceRawData]);
|
|
|
|
const mappedFlockSourceKandangsAvailabilityInfo: {
|
|
available: number;
|
|
unavailable: number;
|
|
} = useMemo(() => {
|
|
if (!mappedFlockSourceKandangsAvailability)
|
|
return { available: 0, unavailable: 0 };
|
|
|
|
let countAvailable = 0;
|
|
let countUnavailable = 0;
|
|
|
|
mappedFlockSourceKandangsAvailability.forEach((item) => {
|
|
if (item.available_qty > 0) {
|
|
countAvailable += 1;
|
|
} else {
|
|
countUnavailable += 1;
|
|
}
|
|
});
|
|
|
|
return { available: countAvailable, unavailable: countUnavailable };
|
|
}, [mappedFlockSourceKandangsAvailability]);
|
|
|
|
const { data: flockDestinationKandangsMaxTargetQty } = useSWR(
|
|
formik.values.flockDestination
|
|
? [
|
|
'transfer-to-laying',
|
|
'max-target-qty',
|
|
String(formik.values.flockDestination.value),
|
|
]
|
|
: undefined,
|
|
([, , id]: string[]) =>
|
|
TransferToLayingApi.getMappedFlockKandangsMaxTargetQty(Number(id))
|
|
);
|
|
|
|
const mappedFlockDestinationKandangsMaxTargetQty: {
|
|
kandang_name: string;
|
|
max_target_qty: number;
|
|
project_flock_kandang_id: number;
|
|
}[] = useMemo(() => {
|
|
if (
|
|
!flockDestinationKandangsMaxTargetQty ||
|
|
!selectedFlockDestinationRawData
|
|
)
|
|
return [];
|
|
|
|
return selectedFlockDestinationRawData
|
|
? selectedFlockDestinationRawData.kandangs.map((kandang) => {
|
|
const maxQty =
|
|
flockDestinationKandangsMaxTargetQty[
|
|
kandang.project_flock_kandang_id
|
|
]?.max_target_qty;
|
|
|
|
return {
|
|
kandang_name: kandang.name,
|
|
max_target_qty: maxQty,
|
|
project_flock_kandang_id: kandang.project_flock_kandang_id,
|
|
};
|
|
})
|
|
: [];
|
|
}, [flockDestinationKandangsMaxTargetQty, selectedFlockDestinationRawData]);
|
|
|
|
const mappedFlockDestinationKandangsAvailabilityInfo: {
|
|
available: number;
|
|
unavailable: number;
|
|
} = useMemo(() => {
|
|
if (!selectedFlockDestinationRawData)
|
|
return { available: 0, unavailable: 0 };
|
|
|
|
let countAvailable = 0;
|
|
let countUnavailable = 0;
|
|
|
|
mappedFlockDestinationKandangsMaxTargetQty.forEach((item) => {
|
|
if (item.max_target_qty > 0) {
|
|
countAvailable += 1;
|
|
} else {
|
|
countUnavailable += 1;
|
|
}
|
|
});
|
|
|
|
return { available: countAvailable, unavailable: countUnavailable };
|
|
}, [mappedFlockDestinationKandangsMaxTargetQty]);
|
|
|
|
const totalTransferedChicken = formik.values.flockDestinationKandangs.reduce(
|
|
(acc, item) => acc + Number(item.quantity),
|
|
0
|
|
);
|
|
|
|
// Sisa transfer = Max available dari kandang asal - Total yang sudah diisi di kandang tujuan
|
|
const totalAvailableChickenForTransfer =
|
|
maxSourceQuantity - totalTransferedChicken;
|
|
|
|
const isNextButtonDisabled = useMemo(() => {
|
|
if (step === 1) {
|
|
return Boolean(
|
|
!formik.values.transfer_date ||
|
|
!formik.values.flockSource ||
|
|
!formik.values.flockDestination
|
|
);
|
|
}
|
|
|
|
if (step === 2) {
|
|
return Boolean(
|
|
!formik.values.flockSourceKandangs.length ||
|
|
!formik.values.flockDestinationKandangs.length
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}, [step, formik.values]);
|
|
|
|
const nextButtonHandler = () => {
|
|
setStep(step + 1);
|
|
};
|
|
|
|
const deleteEnteredKandangHandler = () => {
|
|
formik.setFieldValue('flockSourceKandangs', []);
|
|
formik.setFieldValue('flockDestinationKandangs', []);
|
|
formik.setFieldValue('totalQuantity', '');
|
|
formik.setFieldValue('maxTotalQuantity', '');
|
|
formik.setFieldValue('reason', '');
|
|
formik.setFieldTouched('reason', false);
|
|
setMaxSourceQuantity(0);
|
|
|
|
setStep(2);
|
|
};
|
|
|
|
const flockSourceChangeHandler = (val: OptionType | OptionType[] | null) => {
|
|
formik.setFieldValue('flockSource', val);
|
|
formik.setFieldValue('flockSourceKandangs', []);
|
|
setMaxSourceQuantity(0);
|
|
};
|
|
|
|
const flockDestinationChangeHandler = (
|
|
val: OptionType | OptionType[] | null
|
|
) => {
|
|
formik.setFieldValue('flockDestination', val);
|
|
formik.setFieldValue('flockDestinationKandangs', []);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (modalAction === 'add' || modalAction === 'edit') {
|
|
formModal.openModal();
|
|
}
|
|
}, [modalAction]);
|
|
|
|
useEffect(() => {
|
|
const getFilledInitialValues = async () => {
|
|
if (transferToLayingId && isResponseSuccess(transferToLaying)) {
|
|
const filledInitialValues =
|
|
await getFilledTransferToLayingFormInitialValues(
|
|
transferToLaying.data
|
|
);
|
|
|
|
formik.setValues(filledInitialValues);
|
|
setStep(3);
|
|
}
|
|
|
|
if (isResponseError(transferToLaying)) {
|
|
router.push('/production/transfer-to-laying');
|
|
closeModalHandler();
|
|
toast.error(transferToLaying.message);
|
|
}
|
|
};
|
|
|
|
const getFlockSourceData = async () => {
|
|
if (transferToLayingId && isResponseSuccess(transferToLaying)) {
|
|
const singleFlockSourceRawData = await ProjectFlockApi.getSingle(
|
|
transferToLaying.data.from_project_flock.id
|
|
);
|
|
|
|
if (isResponseSuccess(singleFlockSourceRawData)) {
|
|
setSelectedFlockSourceRawData(singleFlockSourceRawData.data);
|
|
}
|
|
}
|
|
};
|
|
|
|
getFlockSourceData();
|
|
getFilledInitialValues();
|
|
}, [transferToLayingId, transferToLaying]);
|
|
|
|
useEffect(() => {
|
|
if (isResponseSuccess(flockSourceRawData)) {
|
|
const currentSelectedFlockSourceRawData = flockSourceRawData.data.find(
|
|
(item) => item.id === formik.values.flockSource?.value
|
|
);
|
|
|
|
setSelectedFlockSourceRawData(currentSelectedFlockSourceRawData);
|
|
}
|
|
}, [flockSourceRawData, formikFlockSource]);
|
|
|
|
useEffect(() => {
|
|
formik.setFieldValue('totalQuantity', totalTransferedChicken);
|
|
formik.setFieldValue('maxTotalQuantity', totalTransferedChicken);
|
|
}, [totalTransferedChicken, formik.values.flockDestinationKandangs]);
|
|
|
|
// Auto-fill source kandang quantity from total destination quantity
|
|
useEffect(() => {
|
|
if (formik.values.flockSourceKandangs.length > 0) {
|
|
formik.setFieldValue(
|
|
'flockSourceKandangs.0.quantity',
|
|
totalTransferedChicken
|
|
);
|
|
}
|
|
}, [totalTransferedChicken]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
formik.values.flockSourceKandangs.length > 0 &&
|
|
formik.values.flockSourceKandangs[0].maxQuantity &&
|
|
maxSourceQuantity === 0
|
|
) {
|
|
setMaxSourceQuantity(formik.values.flockSourceKandangs[0].maxQuantity);
|
|
}
|
|
}, [formik.values.flockSourceKandangs, maxSourceQuantity]);
|
|
|
|
return (
|
|
<>
|
|
<Modal
|
|
ref={formModal.ref}
|
|
position='end'
|
|
className={{
|
|
modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none',
|
|
}}
|
|
>
|
|
<form
|
|
onSubmit={handleFormSubmit}
|
|
className='w-full min-h-full flex flex-col sm:flex-row items-stretch bg-base-100 rounded-xl overflow-y-auto'
|
|
>
|
|
{/* 1st Section */}
|
|
<div className='w-full sm:w-[446px]'>
|
|
<div className='w-full p-4 flex flex-row items-stretch gap-3 border-b border-base-content/10'>
|
|
<Button
|
|
type='button'
|
|
variant='ghost'
|
|
color='none'
|
|
onClick={() => closeModalHandler()}
|
|
className='p-0 text-black hover:text-base-content'
|
|
>
|
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
|
</Button>
|
|
|
|
<div className='w-px border-none bg-base-content/10' />
|
|
|
|
<h4 className='text-sm font-medium text-base-content/50'>
|
|
{modalAction === 'add' ? 'Add' : 'Edit'} Transfer to Laying
|
|
</h4>
|
|
</div>
|
|
|
|
<div className='w-full p-4 flex flex-col'>
|
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
|
Informasi Umum
|
|
</h4>
|
|
|
|
<DateInput
|
|
required
|
|
name='transfer_date'
|
|
label='Tanggal'
|
|
placeholder='Tanggal'
|
|
value={formik.values.transfer_date ?? ''}
|
|
onChange={formik.handleChange}
|
|
onBlur={formik.handleBlur}
|
|
isError={
|
|
formik.touched.transfer_date &&
|
|
Boolean(formik.errors.transfer_date)
|
|
}
|
|
errorMessage={formik.errors.transfer_date}
|
|
disabled={step > 2}
|
|
/>
|
|
|
|
<SelectInputRadio
|
|
required
|
|
label='Flock Asal'
|
|
placeholder='Pilih Flock Asal'
|
|
value={formik.values.flockSource}
|
|
onChange={flockSourceChangeHandler}
|
|
options={flockSourceOptions}
|
|
isLoading={isLoadingFlockSourceOptions}
|
|
onInputChange={setFlockSourceInputValue}
|
|
onMenuScrollToBottom={loadMoreFlockSource}
|
|
isDisabled={step > 2}
|
|
/>
|
|
|
|
<SelectInputRadio
|
|
required
|
|
label='Flock Tujuan'
|
|
placeholder='Pilih Flock Tujuan'
|
|
value={formik.values.flockDestination}
|
|
onChange={flockDestinationChangeHandler}
|
|
options={flockDestinationOptions}
|
|
isLoading={isLoadingFlockDestinationOptions}
|
|
onInputChange={setFlockDestinationInputValue}
|
|
onMenuScrollToBottom={loadMoreFlockDestination}
|
|
isDisabled={step > 2}
|
|
/>
|
|
</div>
|
|
|
|
{step >= 2 && (
|
|
<>
|
|
<div className='w-full p-4 flex flex-col gap-3 border-y border-base-content/10'>
|
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
|
Pilih Kandang Asal
|
|
</h4>
|
|
|
|
<div className='w-fit flex flex-row items-stretch gap-3'>
|
|
<StatusBadge
|
|
color='info'
|
|
text={`Tersedia (${mappedFlockSourceKandangsAvailabilityInfo.available})`}
|
|
className={{ badge: 'text-nowrap' }}
|
|
/>
|
|
|
|
<div className='w-px border-none bg-base-content/10' />
|
|
|
|
<StatusBadge
|
|
color='neutral'
|
|
text={`Tidak Tersedia (${mappedFlockSourceKandangsAvailabilityInfo.unavailable})`}
|
|
className={{ badge: 'text-nowrap' }}
|
|
/>
|
|
</div>
|
|
|
|
<div className='w-full rounded-xl border border-base-content/10'>
|
|
{mappedFlockSourceKandangsAvailability.map(
|
|
(item, itemIdx) => {
|
|
const isAvailable = item.available_qty > 0;
|
|
const isChecked =
|
|
formik.values.flockSourceKandangs.some(
|
|
(k) =>
|
|
k.kandang.value === item.project_flock_kandang_id
|
|
);
|
|
|
|
const flockSourceKandangRadioChangeHandler = () => {
|
|
if (isAvailable) {
|
|
formik.setFieldValue('flockSourceKandangs', [
|
|
{
|
|
kandang: {
|
|
value: item.project_flock_kandang_id,
|
|
label: item.kandang_name,
|
|
},
|
|
quantity: '',
|
|
maxQuantity: item.available_qty,
|
|
},
|
|
]);
|
|
setMaxSourceQuantity(item.available_qty);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
key={itemIdx}
|
|
className='w-full p-3 flex flex-row items-center justify-between'
|
|
>
|
|
<div className='flex flex-row items-center gap-3'>
|
|
<input
|
|
id={`flock-source-kandang-${item.project_flock_kandang_id}`}
|
|
type='radio'
|
|
name='flockSourceKandang'
|
|
value={item.project_flock_kandang_id}
|
|
checked={isChecked}
|
|
onChange={flockSourceKandangRadioChangeHandler}
|
|
disabled={!isAvailable}
|
|
className={cn('radio radio-md radio-primary', {
|
|
'opacity-50 cursor-not-allowed': !isAvailable,
|
|
})}
|
|
/>
|
|
|
|
<label
|
|
htmlFor={`flock-source-kandang-${item.project_flock_kandang_id}`}
|
|
className={cn('text-sm text-base-content/50', {
|
|
'cursor-pointer': isAvailable,
|
|
'cursor-not-allowed opacity-50': !isAvailable,
|
|
})}
|
|
>
|
|
{item.kandang_name}{' '}
|
|
<span className='text-base-content/20'>{`(Max: ${item.available_qty ?? '-'})`}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<StatusBadge
|
|
color={isAvailable ? 'info' : 'neutral'}
|
|
text={isAvailable ? 'Tersedia' : 'Tidak Tersedia'}
|
|
className={{ badge: 'w-fit' }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className='w-full p-4 flex flex-col gap-3'>
|
|
<div className='flex flex-row items-center justify-between'>
|
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
|
Pilih Kandang Tujuan
|
|
</h4>
|
|
{formik.touched.flockDestinationKandangs &&
|
|
formik.errors.flockDestinationKandangs &&
|
|
typeof formik.errors.flockDestinationKandangs ===
|
|
'string' && (
|
|
<span className='text-xs text-error'>
|
|
{formik.errors.flockDestinationKandangs}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className='w-fit flex flex-row items-stretch gap-3'>
|
|
<StatusBadge
|
|
color='info'
|
|
text={`Tersedia (${mappedFlockDestinationKandangsAvailabilityInfo.available})`}
|
|
className={{ badge: 'text-nowrap' }}
|
|
/>
|
|
|
|
<div className='w-px border-none bg-base-content/10' />
|
|
|
|
<StatusBadge
|
|
color='neutral'
|
|
text={`Tidak Tersedia (${mappedFlockDestinationKandangsAvailabilityInfo.unavailable})`}
|
|
className={{ badge: 'text-nowrap' }}
|
|
/>
|
|
</div>
|
|
|
|
<div className='w-full rounded-xl border border-base-content/10'>
|
|
{mappedFlockDestinationKandangsMaxTargetQty.map(
|
|
(item, itemIdx) => {
|
|
const isAvailable = item.max_target_qty > 0;
|
|
const isChecked =
|
|
formik.values.flockDestinationKandangs.some(
|
|
(k) =>
|
|
k.kandang.value === item.project_flock_kandang_id
|
|
);
|
|
|
|
const flockDestinationKandangCheckboxChangeHandler: FormEventHandler<
|
|
HTMLInputElement
|
|
> = (e) => {
|
|
const checked = (e.target as HTMLInputElement)
|
|
.checked;
|
|
if (checked) {
|
|
formik.setFieldValue('flockDestinationKandangs', [
|
|
...formik.values.flockDestinationKandangs,
|
|
{
|
|
kandang: {
|
|
value: item.project_flock_kandang_id,
|
|
label: item.kandang_name,
|
|
},
|
|
quantity: '',
|
|
maxQuantity: item.max_target_qty,
|
|
},
|
|
]);
|
|
} else {
|
|
formik.setFieldValue(
|
|
'flockDestinationKandangs',
|
|
formik.values.flockDestinationKandangs.filter(
|
|
(k) =>
|
|
k.kandang.value !==
|
|
item.project_flock_kandang_id
|
|
)
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
key={itemIdx}
|
|
className='w-full p-3 flex flex-row items-center justify-between'
|
|
>
|
|
<div className='flex flex-row items-center gap-3'>
|
|
<CheckboxInput
|
|
name={`flockDestinationKandang.${itemIdx}.value`}
|
|
value={item.project_flock_kandang_id}
|
|
checked={isChecked}
|
|
onChange={
|
|
flockDestinationKandangCheckboxChangeHandler
|
|
}
|
|
size='md'
|
|
disabled={!isAvailable}
|
|
classNames={{
|
|
checkbox: cn({
|
|
'bg-base-200 border border-base-content/10 opacity-100':
|
|
!isAvailable,
|
|
}),
|
|
}}
|
|
/>
|
|
|
|
<label
|
|
htmlFor={`flockDestinationKandang.${itemIdx}.value`}
|
|
className={cn('text-sm text-base-content/50', {
|
|
'cursor-pointer': isAvailable,
|
|
'cursor-not-allowed': !isAvailable,
|
|
})}
|
|
>
|
|
{item.kandang_name}{' '}
|
|
<span className='text-base-content/20'>{`(Max: ${item.max_target_qty})`}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<StatusBadge
|
|
color={isAvailable ? 'info' : 'neutral'}
|
|
text={isAvailable ? 'Tersedia' : 'Tidak Tersedia'}
|
|
className={{ badge: 'w-fit' }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<div className='w-full p-4 border-t border-base-content/10'>
|
|
{step < 3 && (
|
|
<Button
|
|
type='button'
|
|
onClick={nextButtonHandler}
|
|
disabled={isNextButtonDisabled}
|
|
className='w-full p-3 rounded-lg text-sm text-base-100'
|
|
>
|
|
Next
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 2nd Section */}
|
|
{step === 3 && (
|
|
<div className='w-full sm:w-[446px] border-l border-base-content/10'>
|
|
<div className='w-full p-4 flex flex-row items-center justify-between gap-3 border-b border-base-content/10'>
|
|
<h4 className='text-sm font-medium text-base-content/50'>
|
|
Tambah Kuantitas
|
|
</h4>
|
|
|
|
<Button
|
|
type='button'
|
|
variant='ghost'
|
|
color='none'
|
|
onClick={deleteEnteredKandangHandler}
|
|
className='p-0 text-error'
|
|
>
|
|
<Icon icon='heroicons:trash' width={20} height={20} />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className='w-full p-4 flex flex-col'>
|
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
|
Informasi Kandang
|
|
</h4>
|
|
|
|
{/* Source Kandang */}
|
|
<div className='flex flex-col'>
|
|
<span className='w-full py-2 text-xs font-semibold'>
|
|
Kandang Asal{' '}
|
|
<span className='tooltip tooltip-error' data-tip='required'>
|
|
<span className='text-error'> *</span>
|
|
</span>
|
|
</span>
|
|
|
|
{formik.values.flockSourceKandangs.length === 0 && (
|
|
<span className='text-sm text-base-content/50 italic'>
|
|
Belum ada kandang asal yang dipilih
|
|
</span>
|
|
)}
|
|
|
|
{formik.values.flockSourceKandangs.length > 0 && (
|
|
<div className='flex flex-col gap-3'>
|
|
{formik.values.flockSourceKandangs.map((item, index) => {
|
|
const isInvalid =
|
|
!Boolean(
|
|
getIn(
|
|
formik.touched,
|
|
`flockSourceKandangs[${index}].quantity`
|
|
)
|
|
) && item.quantity === ''
|
|
? false
|
|
: Boolean(
|
|
getIn(
|
|
formik.errors,
|
|
`flockSourceKandangs[${index}].quantity`
|
|
)
|
|
);
|
|
|
|
const errorMessage = getIn(
|
|
formik.errors,
|
|
`flockSourceKandangs[${index}].quantity`
|
|
);
|
|
|
|
return (
|
|
<NumberInput
|
|
key={`flockSourceKandangs-${item.kandang.value}-${index}`}
|
|
name={`flockSourceKandangs.${index}.quantity`}
|
|
placeholder='Masukkan Kuantitas pada Kandang Tujuan'
|
|
value={item.quantity}
|
|
onChange={formik.handleChange}
|
|
isError={isInvalid}
|
|
errorMessage={errorMessage}
|
|
inputPrefix={
|
|
<div className='w-full h-full py-1 flex flex-row items-stretch justify-between gap-5'>
|
|
<span
|
|
title={item.kandang.label}
|
|
className='text-sm text-base-content self-center text-nowrap truncate'
|
|
>
|
|
{item.kandang.label}
|
|
</span>
|
|
|
|
<div className='w-px bg-base-content/10' />
|
|
</div>
|
|
}
|
|
readOnly
|
|
disabled
|
|
className={{
|
|
inputPrefix:
|
|
'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0',
|
|
inputPrefixSuffixWrapper: 'grid grid-cols-2',
|
|
inputWrapper: 'border-l-0 pl-5',
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Destination Kandang */}
|
|
<div className='mt-3 flex flex-col'>
|
|
<span className='w-fit py-2 text-xs font-semibold flex flex-row items-center gap-3'>
|
|
<span className='text-nowrap'>
|
|
Kandang Tujuan{' '}
|
|
<span
|
|
className='tooltip tooltip-error'
|
|
data-tip='required'
|
|
>
|
|
<span className='text-error'> *</span>
|
|
</span>
|
|
</span>
|
|
|
|
<div className='w-px h-5 bg-base-content/10' />
|
|
|
|
<StatusBadge
|
|
color={
|
|
totalAvailableChickenForTransfer < 0
|
|
? 'error'
|
|
: 'neutral'
|
|
}
|
|
text={`Sisa transfer: ${formatNumber(
|
|
totalAvailableChickenForTransfer,
|
|
'en-US'
|
|
)} ekor`}
|
|
className={{
|
|
badge: 'text-nowrap',
|
|
}}
|
|
/>
|
|
</span>
|
|
|
|
{formik.values.flockDestinationKandangs.length === 0 && (
|
|
<span className='text-sm text-base-content/50 italic'>
|
|
Belum ada kandang tujuan yang dipilih
|
|
</span>
|
|
)}
|
|
|
|
{formik.values.flockDestinationKandangs.length > 0 && (
|
|
<div className='flex flex-col gap-3'>
|
|
{formik.values.flockDestinationKandangs.map(
|
|
(item, index) => {
|
|
const isInvalid =
|
|
!Boolean(
|
|
getIn(
|
|
formik.touched,
|
|
`flockDestinationKandangs.${index}.quantity`
|
|
)
|
|
) && item.quantity === ''
|
|
? false
|
|
: Boolean(
|
|
getIn(
|
|
formik.errors,
|
|
`flockDestinationKandangs[${index}].quantity`
|
|
)
|
|
);
|
|
|
|
const errorMessage = getIn(
|
|
formik.errors,
|
|
`flockDestinationKandangs[${index}].quantity`
|
|
);
|
|
|
|
return (
|
|
<NumberInput
|
|
key={`flockDestinationKandangs-${item.kandang.value}-${index}`}
|
|
name={`flockDestinationKandangs.${index}.quantity`}
|
|
placeholder='Masukkan Kuantitas'
|
|
value={item.quantity}
|
|
onChange={formik.handleChange}
|
|
isError={isInvalid}
|
|
errorMessage={errorMessage}
|
|
inputPrefix={
|
|
<div className='w-full h-full py-1 flex flex-row items-stretch justify-between gap-5'>
|
|
<span
|
|
title={item.kandang.label}
|
|
className='text-sm text-base-content self-center text-nowrap truncate'
|
|
>
|
|
{item.kandang.label}
|
|
</span>
|
|
|
|
<div className='w-px bg-base-content/10' />
|
|
</div>
|
|
}
|
|
className={{
|
|
inputPrefix:
|
|
'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0',
|
|
inputPrefixSuffixWrapper: 'grid grid-cols-2',
|
|
inputWrapper: 'border-l-0 pl-5',
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className='w-full p-4 flex flex-col border-y border-base-content/10'>
|
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
|
Informasi Umum
|
|
</h4>
|
|
|
|
<NumberInput
|
|
name='totalQuantity'
|
|
label='Jumlah Transfer'
|
|
placeholder='Total Kuantitas Transfer'
|
|
value={formik.values.totalQuantity}
|
|
onChange={formik.handleChange}
|
|
onBlur={formik.handleBlur}
|
|
isError={totalAvailableChickenForTransfer < 0}
|
|
errorMessage={
|
|
totalAvailableChickenForTransfer < 0
|
|
? `Jumlah transfer melebihi ketersediaan (${formatNumber(maxSourceQuantity, 'en-US')} ayam)`
|
|
: ''
|
|
}
|
|
disabled
|
|
/>
|
|
|
|
<TextArea
|
|
required
|
|
name='reason'
|
|
label='Catatan'
|
|
placeholder='Alasan Transfer'
|
|
rows={4}
|
|
value={formik.values.reason}
|
|
onChange={formik.handleChange}
|
|
onBlur={formik.handleBlur}
|
|
isError={
|
|
Boolean(formik.touched.reason) &&
|
|
Boolean(formik.errors.reason)
|
|
}
|
|
errorMessage={formik.errors.reason}
|
|
/>
|
|
</div>
|
|
|
|
<div className='w-full p-4 self-end flex flex-col gap-3'>
|
|
{formErrorMessage && (
|
|
<div role='alert' className='alert alert-error w-full'>
|
|
<Icon
|
|
icon='material-symbols:error-outline'
|
|
width={24}
|
|
height={24}
|
|
/>
|
|
<span>{formErrorMessage}</span>
|
|
</div>
|
|
)}
|
|
|
|
<AlertErrorList formErrorList={formErrorList} onClose={close} />
|
|
|
|
<Button
|
|
type='submit'
|
|
disabled={
|
|
formik.isSubmitting || totalAvailableChickenForTransfer < 0
|
|
}
|
|
isLoading={formik.isSubmitting}
|
|
className='w-full p-3 rounded-lg text-sm text-base-100'
|
|
>
|
|
Submit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</form>
|
|
</Modal>
|
|
|
|
<TransferToLayingConfirmationModal
|
|
ref={successModal.ref}
|
|
type='success'
|
|
text={
|
|
submittedActionType === 'edit'
|
|
? 'Data Berhasil Diperbarui'
|
|
: 'Data Berhasil Ditambahkan'
|
|
}
|
|
subtitleText={
|
|
submittedActionType === 'edit'
|
|
? 'Data transfer to laying telah berhasil diperbarui.'
|
|
: 'Data transfer to laying telah berhasil disimpan.'
|
|
}
|
|
transferToLayingForm={formikLastValues}
|
|
onClose={() => {
|
|
setFormikLastValues(undefined);
|
|
setSubmittedActionType(null);
|
|
}}
|
|
secondaryButton={undefined}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default TransferToLayingFormModal;
|