refactor(FE): Refactor InventoryAdjustmentForm to simplify product

handling
This commit is contained in:
rstubryan
2026-03-02 15:24:14 +07:00
parent 968243c370
commit 910ea85b62
@@ -1,10 +1,8 @@
'use client';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import {
InventoryAdjustmentApi,
ProductWarehouseApi,
} from '@/services/api/inventory';
import { InventoryAdjustmentApi } from '@/services/api/inventory';
import { ProductApi } from '@/services/api/master-data';
import {
CreateInventoryAdjustmentPayload,
InventoryAdjustment,
@@ -28,8 +26,7 @@ import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import { components as ReactSelectComponents, OptionProps } from 'react-select';
import StatusBadge from '@/components/helper/StatusBadge';
import TextArea from '@/components/input/TextArea';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
@@ -37,7 +34,7 @@ import { Location } from '@/types/api/master-data/location';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { Kandang } from '@/types/api/master-data/kandang';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { Product } from '@/types/api/master-data/product';
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
import { BaseApiResponse } from '@/types/api/api-general';
import useSWR from 'swr';
@@ -46,7 +43,6 @@ import {
TRANSACTION_SUBTYPE_OPTIONS,
} from '@/config/constant';
import NumberInput from '@/components/input/NumberInput';
import { formatNumber } from '@/lib/helper';
interface InventoryAdjustmentFormProps {
type?: 'add' | 'detail';
@@ -62,8 +58,6 @@ const InventoryAdjustmentForm = ({
InventoryAdjustmentFormErrorMessage,
setInventoryAdjustmentFormErrorMessage,
] = useState('');
const [quantityLabel, setQuantityLabel] = useState('Kuantitas');
const [quantityError, setQuantityError] = useState<string>('');
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null
@@ -188,53 +182,24 @@ const InventoryAdjustmentForm = ({
setInputValue: setProductInputValue,
isLoadingOptions: isLoadingProductOptions,
loadMore: loadMoreProducts,
rawData: productWarehouses,
} = useSelect<ProductWarehouse>(
selectedKandang && selectedProjectFlockLocationId
? ProductWarehouseApi.basePath
: '',
'id',
'product.name',
'search',
{
kandang_id: selectedKandang?.value?.toString() || '',
location_id: selectedProjectFlockLocationId,
}
);
rawData: products,
} = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search');
const productOptions = useMemo(() => {
if (!isResponseSuccess(productWarehouses)) return [];
if (!isResponseSuccess(products)) return [];
const excludedFlags = ['AYAM-AFKIR', 'AYAM-CULLING', 'AYAM-MATI'];
const filteredProducts = productWarehouses.data.filter((pw) => {
const productFlags = (pw.product.flags as string[]) || [];
const filteredProducts = products.data.filter((product) => {
const productFlags = (product.flags as string[]) || [];
return !productFlags.some((flag) => excludedFlags.includes(flag));
});
return filteredProducts.map((pw) => ({
value: pw.product.id,
label: pw.product.name,
quantity: pw.quantity,
flags: pw.product.flags,
return filteredProducts.map((product) => ({
value: product.id,
label: product.name,
flags: product.flags,
}));
}, [productWarehouses]);
const selectedProductQuantity = useMemo(() => {
if (!selectedProduct) return 0;
const product = productOptions.find(
(opt) => opt.value === selectedProduct.value
);
return product?.quantity ?? 0;
}, [selectedProduct, productOptions]);
const isStockOutSubtype = useMemo(() => {
const subtype = selectedTransactionSubtype?.value;
return (
subtype === 'RECORDING_STOCK_OUT' ||
subtype === 'RECORDING_DEPLETION_OUT' ||
subtype === 'MARKETING_OUT'
);
}, [selectedTransactionSubtype]);
}, [products]);
const selectedProductFlags = useMemo(() => {
if (!selectedProduct) return [];
@@ -244,31 +209,6 @@ const InventoryAdjustmentForm = ({
return (product?.flags as string[]) || [];
}, [selectedProduct, productOptions]);
const ProductOption = useMemo(() => {
const OptionComponent = (
props: OptionProps<OptionType & { quantity?: number }, boolean>
) => {
const { data, children } = props;
const quantity = data.quantity ?? 0;
const isAvailable = quantity > 0;
return (
<ReactSelectComponents.Option {...props}>
<div className='flex items-center justify-between gap-2'>
<span className='flex-1'>{children}</span>
<StatusBadge
color={isAvailable ? 'success' : 'error'}
text={isAvailable ? 'Tersedia' : 'Kosong'}
className={{ badge: 'whitespace-nowrap w-fit' }}
/>
</div>
</ReactSelectComponents.Option>
);
};
OptionComponent.displayName = 'ProductOption';
return OptionComponent;
}, []);
const kandangOptions = useMemo(() => {
let options: OptionType[] = [];
@@ -378,42 +318,6 @@ const InventoryAdjustmentForm = ({
return transactionType === 'PEMBELIAN' || transactionType === 'PENJUALAN';
}, [selectedTransactionType]);
useEffect(() => {
const subtype = selectedTransactionSubtype?.value;
if (
subtype === 'RECORDING_STOCK_OUT' ||
subtype === 'RECORDING_DEPLETION_OUT' ||
subtype === 'MARKETING_OUT'
) {
setQuantityLabel('Kurangi Stok');
} else if (subtype === 'RECORDING_EGG_IN' || subtype === 'PURCHASE_IN') {
setQuantityLabel('Tambah Stok');
} else {
setQuantityLabel('Kuantitas');
}
}, [selectedTransactionSubtype]);
useEffect(() => {
const qty = Number(formik.values.qty);
if (
isStockOutSubtype &&
selectedProduct &&
qty > 0 &&
qty > selectedProductQuantity
) {
setQuantityError(
`Kuantitas ${formatNumber(qty)} melebihi stok tersedia (${formatNumber(selectedProductQuantity)})`
);
} else {
setQuantityError('');
}
}, [
formik.values.qty,
isStockOutSubtype,
selectedProduct,
selectedProductQuantity,
]);
useEffect(() => {
if (selectedTransactionType?.value === 'RECORDING' && selectedProduct) {
setSelectedTransactionSubtype(null);
@@ -541,7 +445,6 @@ const InventoryAdjustmentForm = ({
const resetHandler = () => {
formik.resetForm();
setQuantityLabel('Kuantitas');
setSelectedLocation(null);
setSelectedProjectFlock(null);
setSelectedKandang(null);
@@ -764,7 +667,6 @@ const InventoryAdjustmentForm = ({
onChange={productChangeHandler}
onInputChange={setProductInputValue}
options={productOptions}
optionComponent={ProductOption}
onMenuScrollToBottom={loadMoreProducts}
isLoading={isLoadingProductOptions}
isError={
@@ -777,14 +679,6 @@ const InventoryAdjustmentForm = ({
? 'Pilih Produk'
: 'Pilih Kandang terlebih dahulu'
}
inputPrefix={
selectedProduct
? 'Stock: ' +
(productOptions.find(
(opt) => opt.value === selectedProduct?.value
)?.quantity ?? 0)
: undefined
}
isClearable
isSearchable
/>
@@ -841,22 +735,14 @@ const InventoryAdjustmentForm = ({
wrapper: `${selectedTransactionSubtype ? '' : 'hidden'}`,
}}
required
label={quantityLabel}
label='Kuantitas'
name='qty'
value={formik.values.qty}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
isError={
(formik.touched.qty && Boolean(formik.errors.qty)) ||
Boolean(quantityError)
}
errorMessage={(formik.errors.qty as string) || quantityError}
isError={formik.touched.qty && Boolean(formik.errors.qty)}
errorMessage={formik.errors.qty as string}
readOnly={type === 'detail'}
bottomLabel={
selectedProduct
? `Tersedia: ${formatNumber(selectedProductQuantity)}`
: undefined
}
/>
{/* Number Input Price */}
<NumberInput
@@ -905,7 +791,7 @@ const InventoryAdjustmentForm = ({
type='submit'
color='primary'
isLoading={formik.isSubmitting}
disabled={formik.isSubmitting || Boolean(quantityError)}
disabled={formik.isSubmitting}
className='px-4 w-full sm:w-auto'
>
Submit