refactor(FE): Refactor InventoryAdjustmentForm to improve state

management
This commit is contained in:
rstubryan
2026-02-26 10:38:41 +07:00
parent da82c704d5
commit e5007a285a
2 changed files with 162 additions and 109 deletions
@@ -81,11 +81,13 @@ export const InventoryAdjustmentFormSchema: Yup.ObjectSchema<InventoryAdjustment
.required('Produk wajib diisi!')
.typeError('Produk wajib diisi!'),
transaction_type: Yup.string()
.min(1, 'Tipe transaksi wajib diisi!')
.oneOf(
['PEMBELIAN', 'PENJUALAN', 'BIAYA', 'RECORDING'],
['PEMBELIAN', 'PENJUALAN', 'RECORDING'],
'Tipe transaksi tidak valid'
)
.required('Tipe transaksi wajib diisi'),
.required('Tipe transaksi wajib diisi')
.typeError('Tipe transaksi wajib diisi!'),
transaction_subtype: Yup.string().required(
'Sub tipe transaksi wajib diisi'
),
@@ -25,7 +25,6 @@ import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import TextInput from '@/components/input/TextInput';
import TextArea from '@/components/input/TextArea';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
@@ -52,7 +51,6 @@ const InventoryAdjustmentForm = ({
type = 'add',
initialValues,
}: InventoryAdjustmentFormProps) => {
// State
const router = useRouter();
const [
InventoryAdjustmentFormErrorMessage,
@@ -60,7 +58,6 @@ const InventoryAdjustmentForm = ({
] = useState('');
const [quantityLabel, setQuantityLabel] = useState('Kuantitas');
// Selected States untuk cascading selects
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null
);
@@ -69,10 +66,16 @@ const InventoryAdjustmentForm = ({
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
null
);
const [selectedProduct, setSelectedProduct] = useState<OptionType | null>(
null
);
const [selectedTransactionType, setSelectedTransactionType] =
useState<OptionType | null>(null);
const [selectedTransactionSubtype, setSelectedTransactionSubtype] =
useState<OptionType | null>(null);
const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
useState<string>('');
// Submit Handler
const createInventoryAdjustmentHandler = useCallback(
async (payload: CreateInventoryAdjustmentPayload) => {
const createInventoryAdjustmentRes =
@@ -91,7 +94,6 @@ const InventoryAdjustmentForm = ({
[router]
);
// API Data Fetching
const {
setInputValue: setLocationInputValue,
options: locationOptions,
@@ -115,7 +117,6 @@ const InventoryAdjustmentForm = ({
}
);
// Lookup URL untuk mendapatkan project_flock_kandang_id
const projectFlockKandangLookupUrl = useMemo(() => {
if (!selectedProjectFlock || !selectedKandang) return null;
const params = new URLSearchParams({
@@ -140,7 +141,6 @@ const InventoryAdjustmentForm = ({
? projectFlockKandangLookupData.data
: undefined;
// Fetch project_flock_kandang detail untuk edit mode saja (tidak perlu untuk detail)
const projectFlockKandangDetailUrl = useMemo(() => {
if (type !== 'edit' || !initialValues?.project_flock_kandang_id)
return null;
@@ -162,7 +162,6 @@ const InventoryAdjustmentForm = ({
? projectFlockKandangDetailData.data
: undefined;
// Fetch approved project flock kandangs untuk filter kandang options
const approvedProjectFlockKandangsUrl = useMemo(() => {
const params = new URLSearchParams({
step_name: 'Disetujui',
@@ -181,7 +180,6 @@ const InventoryAdjustmentForm = ({
return approvedProjectFlockKandangsData.data;
}, [approvedProjectFlockKandangsData]);
// Product select dengan filter project_flock_kandang_id - hanya fetch jika project_flock_kandang_id ada
const productUrl = useMemo(() => {
if (!projectFlockKandangLookup?.project_flock_kandang_id) return null;
const params = new URLSearchParams({
@@ -214,7 +212,6 @@ const InventoryAdjustmentForm = ({
// Implementasi load more jika diperlukan
}, []);
// Kandang options dari project flock data (filtered by approved status untuk add mode)
const kandangOptions = useMemo(() => {
let options: OptionType[] = [];
@@ -225,14 +222,12 @@ const InventoryAdjustmentForm = ({
);
if (selectedProjectFlockData?.kandangs) {
// Get approved kandang ids untuk project flock yang dipilih
const approvedKandangIds = approvedProjectFlockKandangs
.filter((pfk) => pfk.project_flock_id === selectedProjectFlock.value)
.map((pfk) => pfk.kandang_id);
const kandangOptions = selectedProjectFlockData.kandangs
.filter((kandang: Kandang) => {
// Untuk add mode, hanya tampilkan kandang yang approved
if (type === 'add') {
return approvedKandangIds.includes(kandang.id);
}
@@ -268,7 +263,6 @@ const InventoryAdjustmentForm = ({
approvedProjectFlockKandangs,
]);
// Enhanced options untuk edit/detail
const enhancedLocationOptions = useMemo(() => {
const options = [...locationOptions];
@@ -307,7 +301,6 @@ const InventoryAdjustmentForm = ({
return options;
}, [projectFlockOptions, projectFlockKandangDetail, type]);
// Formik Initial Values
const formikInitialValues = useMemo<Partial<InventoryAdjustmentFormValues>>(
() => ({
location: null,
@@ -329,11 +322,12 @@ const InventoryAdjustmentForm = ({
[]
);
// Formik
const formik = useFormik<InventoryAdjustmentFormValues>({
enableReinitialize: false,
initialValues: formikInitialValues as InventoryAdjustmentFormValues,
validationSchema: InventoryAdjustmentFormSchema,
validateOnChange: true,
validateOnBlur: true,
onSubmit: async (values) => {
setInventoryAdjustmentFormErrorMessage('');
const payload: CreateInventoryAdjustmentPayload = {
@@ -353,26 +347,23 @@ const InventoryAdjustmentForm = ({
},
});
// Transaction subtype options berdasarkan transaction_type
const transactionSubtypeOptions = useMemo(() => {
const transactionType = formik.values.transaction_type;
const transactionType = selectedTransactionType?.value;
if (transactionType === 'RECORDING') {
return TRANSACTION_SUBTYPE_OPTIONS.RECORDING;
}
return [];
}, [formik.values.transaction_type]);
}, [selectedTransactionType]);
// Cek apakah subtype readonly (untuk PEMBELIAN/PENJUALAN)
const isTransactionSubtypeReadonly = useMemo(() => {
const transactionType = formik.values.transaction_type;
const transactionType = selectedTransactionType?.value;
return transactionType === 'PEMBELIAN' || transactionType === 'PENJUALAN';
}, [formik.values.transaction_type]);
}, [selectedTransactionType]);
// Update quantity label berdasarkan transaction_subtype
useEffect(() => {
const subtype = formik.values.transaction_subtype;
const subtype = selectedTransactionSubtype?.value;
if (
subtype === 'RECORDING_STOCK_OUT' ||
subtype === 'RECORDING_DEPLETION_OUT' ||
@@ -389,9 +380,9 @@ const InventoryAdjustmentForm = ({
} else {
setQuantityLabel('Kuantitas');
}
}, [formik.values.transaction_subtype]);
}, [selectedTransactionSubtype]);
// Event Handlers
// ===== EVENT HANDLERS =====
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
const location = val as OptionType | null;
const locationId = location ? Number(location.value) : 0;
@@ -402,21 +393,12 @@ const InventoryAdjustmentForm = ({
formik.setFieldValue('location_id', locationId);
setSelectedLocation(location);
setSelectedProjectFlock(null);
setSelectedKandang(null);
setSelectedProduct(null);
setSelectedProjectFlockLocationId(
location ? location.value.toString() : ''
);
// Reset dependent fields
setSelectedProjectFlock(null);
setSelectedKandang(null);
formik.setFieldValue('project_flock', null);
formik.setFieldValue('project_flock_id', 0);
formik.setFieldValue('kandang', null);
formik.setFieldValue('kandang_id', 0);
formik.setFieldValue('project_flock_kandang', null);
formik.setFieldValue('project_flock_kandang_id', 0);
formik.setFieldValue('product', null);
formik.setFieldValue('product_id', 0);
};
const projectFlockChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -430,14 +412,7 @@ const InventoryAdjustmentForm = ({
setSelectedProjectFlock(projectFlock);
setSelectedKandang(null);
// Reset dependent fields
formik.setFieldValue('kandang', null);
formik.setFieldValue('kandang_id', 0);
formik.setFieldValue('project_flock_kandang', null);
formik.setFieldValue('project_flock_kandang_id', 0);
formik.setFieldValue('product', null);
formik.setFieldValue('product_id', 0);
setSelectedProduct(null);
};
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -450,52 +425,68 @@ const InventoryAdjustmentForm = ({
formik.setFieldValue('kandang_id', kandangId);
setSelectedKandang(kandang);
// Reset product karena kandang berubah
formik.setFieldValue('product', null);
formik.setFieldValue('product_id', 0);
setSelectedProduct(null);
formik.setFieldTouched('project_flock_kandang', true);
formik.setFieldTouched('project_flock_kandang_id', true);
};
const productChangeHandler = (val: OptionType | OptionType[] | null) => {
const product = val as OptionType | null;
const productId = product?.value ?? 0;
formik.setFieldTouched('product', true);
formik.setFieldValue('product', val);
formik.setFieldValue('product', product);
formik.setFieldTouched('product_id', true);
formik.setFieldValue('product_id', (val as OptionType)?.value ?? 0);
formik.setFieldValue('product_id', productId);
setSelectedProduct(product);
};
const transactionTypeChangeHandler = (
val: OptionType | OptionType[] | null
) => {
const selectedType = (val as OptionType)?.value as string;
const typeOption = val as OptionType | null;
const selectedType = typeOption?.value as string;
formik.setFieldTouched('transaction_type', true);
formik.setFieldValue('transaction_type', selectedType);
formik.setFieldTouched('transaction_type', true);
setSelectedTransactionType(typeOption);
setSelectedTransactionSubtype(null);
// Reset transaction_subtype
formik.setFieldValue('transaction_subtype', '');
// Auto-fill transaction_subtype untuk PEMBELIAN dan PENJUALAN
if (selectedType === 'PEMBELIAN') {
formik.setFieldValue(
'transaction_subtype',
TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.value
);
setSelectedTransactionSubtype({
value: TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.value,
label: TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.label,
});
} else if (selectedType === 'PENJUALAN') {
formik.setFieldValue(
'transaction_subtype',
TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.value
);
setSelectedTransactionSubtype({
value: TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.value,
label: TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.label,
});
}
};
const transactionSubtypeChangeHandler = (
val: OptionType | OptionType[] | null
) => {
const selectedSubtype = (val as OptionType)?.value as string;
formik.setFieldTouched('transaction_subtype', true);
const subtypeOption = val as OptionType | null;
const selectedSubtype = subtypeOption?.value as string;
formik.setFieldValue('transaction_subtype', selectedSubtype);
formik.setFieldTouched('transaction_subtype', true);
setSelectedTransactionSubtype(subtypeOption);
};
const resetHandler = () => {
@@ -504,10 +495,12 @@ const InventoryAdjustmentForm = ({
setSelectedLocation(null);
setSelectedProjectFlock(null);
setSelectedKandang(null);
setSelectedProduct(null);
setSelectedTransactionType(null);
setSelectedTransactionSubtype(null);
setSelectedProjectFlockLocationId('');
};
// Effect - Set project_flock_kandang_id dari lookup
useEffect(() => {
if (projectFlockKandangLookup?.project_flock_kandang_id) {
const projectFlockKandangId =
@@ -523,12 +516,10 @@ const InventoryAdjustmentForm = ({
}
}, [projectFlockKandangLookup, formik.values.project_flock_kandang_id]);
// Effect - Set initial values untuk edit mode (dengan projectFlockKandangDetail)
useEffect(() => {
if (initialValues && type === 'edit') {
const transactionSubtype = initialValues.transaction_subtype;
// Determine transaction_type dari transaction_subtype
let transactionType = '';
if (transactionSubtype === 'PURCHASE_IN') {
transactionType = 'PEMBELIAN';
@@ -538,7 +529,6 @@ const InventoryAdjustmentForm = ({
transactionType = 'RECORDING';
}
// Set lokasi
if (initialValues.location) {
const locationOption = {
value: initialValues.location.id,
@@ -548,7 +538,6 @@ const InventoryAdjustmentForm = ({
setSelectedProjectFlockLocationId(initialValues.location.id.toString());
}
// Set project flock
if (initialValues.project_flock) {
const projectFlockOption = {
value: initialValues.project_flock.id,
@@ -557,7 +546,6 @@ const InventoryAdjustmentForm = ({
setSelectedProjectFlock(projectFlockOption);
}
// Set kandang dari project_flock_kandang jika ada (hanya untuk edit mode)
if (projectFlockKandangDetail) {
const kandangOption = {
value: projectFlockKandangDetail.kandang.id,
@@ -566,6 +554,43 @@ const InventoryAdjustmentForm = ({
setSelectedKandang(kandangOption);
}
if (initialValues.product_warehouse?.product) {
const productOption = {
value: initialValues.product_warehouse.product.id,
label: initialValues.product_warehouse.product.name,
};
setSelectedProduct(productOption);
}
if (transactionType) {
const typeOption = {
value: transactionType,
label:
TRANSACTION_TYPE_OPTIONS.find(
(opt) => opt.value === transactionType
)?.label || '',
};
setSelectedTransactionType(typeOption);
}
if (transactionSubtype) {
let subtypeLabel = '';
if (transactionSubtype === 'PURCHASE_IN') {
subtypeLabel = TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.label;
} else if (transactionSubtype === 'MARKETING_OUT') {
subtypeLabel = TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.label;
} else {
subtypeLabel =
TRANSACTION_SUBTYPE_OPTIONS.RECORDING.find(
(opt) => opt.value === transactionSubtype
)?.label || '';
}
setSelectedTransactionSubtype({
value: transactionSubtype,
label: subtypeLabel,
});
}
formik.setValues({
location: initialValues.location
? {
@@ -611,12 +636,10 @@ const InventoryAdjustmentForm = ({
}
}, [formik.setValues, initialValues, projectFlockKandangDetail, type]);
// Effect - Set initial values untuk detail mode (tanpa projectFlockKandangDetail)
useEffect(() => {
if (initialValues && type === 'detail') {
const transactionSubtype = initialValues.transaction_subtype;
// Determine transaction_type dari transaction_subtype
let transactionType = '';
if (transactionSubtype === 'PURCHASE_IN') {
transactionType = 'PEMBELIAN';
@@ -626,6 +649,43 @@ const InventoryAdjustmentForm = ({
transactionType = 'RECORDING';
}
if (initialValues.product_warehouse?.product) {
const productOption = {
value: initialValues.product_warehouse.product.id,
label: initialValues.product_warehouse.product.name,
};
setSelectedProduct(productOption);
}
if (transactionType) {
const typeOption = {
value: transactionType,
label:
TRANSACTION_TYPE_OPTIONS.find(
(opt) => opt.value === transactionType
)?.label || '',
};
setSelectedTransactionType(typeOption);
}
if (transactionSubtype) {
let subtypeLabel = '';
if (transactionSubtype === 'PURCHASE_IN') {
subtypeLabel = TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.label;
} else if (transactionSubtype === 'MARKETING_OUT') {
subtypeLabel = TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.label;
} else {
subtypeLabel =
TRANSACTION_SUBTYPE_OPTIONS.RECORDING.find(
(opt) => opt.value === transactionSubtype
)?.label || '';
}
setSelectedTransactionSubtype({
value: transactionSubtype,
label: subtypeLabel,
});
}
formik.setValues({
location: initialValues.location
? {
@@ -641,7 +701,7 @@ const InventoryAdjustmentForm = ({
}
: null,
project_flock_id: initialValues.project_flock?.id ?? 0,
kandang: null, // Tidak perlu kandang untuk detail mode
kandang: null,
kandang_id: 0,
project_flock_kandang: initialValues.project_flock_kandang_id
? {
@@ -699,7 +759,7 @@ const InventoryAdjustmentForm = ({
<SelectInput
required
label='Lokasi'
value={formik.values.location as OptionType}
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
options={enhancedLocationOptions}
@@ -710,14 +770,16 @@ const InventoryAdjustmentForm = ({
}
errorMessage={formik.errors.location_id as string}
isDisabled={type === 'detail'}
placeholder='Pilih Lokasi'
isClearable
isSearchable
/>
{/* Select Input Project Flock */}
<SelectInput
required
label='Project Flock'
value={formik.values.project_flock as OptionType}
value={selectedProjectFlock}
onChange={projectFlockChangeHandler}
onInputChange={setProjectFlockInputValue}
options={enhancedProjectFlockOptions}
@@ -728,32 +790,42 @@ const InventoryAdjustmentForm = ({
Boolean(formik.errors.project_flock_id)
}
errorMessage={formik.errors.project_flock_id as string}
isDisabled={type === 'detail' || formik.values.location_id === 0}
isDisabled={type === 'detail' || !selectedLocation}
placeholder={
selectedLocation
? 'Pilih Project Flock'
: 'Pilih Lokasi terlebih dahulu'
}
isClearable
isSearchable
/>
{/* Select Input Kandang */}
<SelectInput
required
label='Kandang'
value={formik.values.kandang as OptionType}
value={selectedKandang}
onChange={kandangChangeHandler}
options={kandangOptions}
isError={
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
}
errorMessage={formik.errors.kandang_id as string}
isDisabled={
type === 'detail' || formik.values.project_flock_id === 0
isDisabled={type === 'detail' || !selectedProjectFlock}
placeholder={
selectedProjectFlock
? 'Pilih Kandang'
: 'Pilih Project Flock terlebih dahulu'
}
isClearable
isSearchable
/>
{/* Select Input Product */}
<SelectInput
required
label='Produk'
value={formik.values.product as OptionType}
value={selectedProduct}
onChange={productChangeHandler}
onInputChange={setProductInputValue}
options={productOptions}
@@ -763,28 +835,21 @@ const InventoryAdjustmentForm = ({
formik.touched.product_id && Boolean(formik.errors.product_id)
}
errorMessage={formik.errors.product_id as string}
isDisabled={
type === 'detail' ||
formik.values.project_flock_kandang_id === 0
isDisabled={type === 'detail' || !selectedKandang}
placeholder={
selectedKandang
? 'Pilih Produk'
: 'Pilih Kandang terlebih dahulu'
}
isClearable
isSearchable
/>
{/* Select Input Transaction Type */}
<SelectInput
required
label='Tipe Transaksi'
value={
formik.values.transaction_type
? {
value: formik.values.transaction_type,
label:
TRANSACTION_TYPE_OPTIONS.find(
(opt) => opt.value === formik.values.transaction_type
)?.label || '',
}
: null
}
value={selectedTransactionType}
onChange={transactionTypeChangeHandler}
options={TRANSACTION_TYPE_OPTIONS}
isError={
@@ -795,29 +860,14 @@ const InventoryAdjustmentForm = ({
isDisabled={type === 'detail'}
placeholder='Pilih Tipe Transaksi'
isClearable
isSearchable
/>
{/* Select Input Transaction Subtype */}
<SelectInput
required
label='Sub Tipe Transaksi'
value={
formik.values.transaction_subtype
? {
value: formik.values.transaction_subtype,
label:
formik.values.transaction_type === 'PEMBELIAN'
? TRANSACTION_SUBTYPE_OPTIONS.PEMBELIAN.label
: formik.values.transaction_type === 'PENJUALAN'
? TRANSACTION_SUBTYPE_OPTIONS.PENJUALAN.label
: TRANSACTION_SUBTYPE_OPTIONS.RECORDING.find(
(opt) =>
opt.value ===
formik.values.transaction_subtype
)?.label || '',
}
: null
}
value={selectedTransactionSubtype}
onChange={transactionSubtypeChangeHandler}
options={transactionSubtypeOptions}
isError={
@@ -828,22 +878,23 @@ const InventoryAdjustmentForm = ({
isDisabled={
type === 'detail' ||
isTransactionSubtypeReadonly ||
formik.values.transaction_type === ''
!selectedTransactionType
}
placeholder={
formik.values.transaction_type === ''
!selectedTransactionType
? 'Pilih Tipe Transaksi terlebih dahulu'
: isTransactionSubtypeReadonly
? 'Otomatis terisi'
: 'Pilih Sub Tipe Transaksi'
}
isClearable
isSearchable
/>
{/* Number Input Quantity */}
<NumberInput
className={{
wrapper: `${formik.values.transaction_subtype !== '' ? '' : 'hidden'}`,
wrapper: `${selectedTransactionSubtype ? '' : 'hidden'}`,
}}
required
label={quantityLabel}