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'; 'use client';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { import { InventoryAdjustmentApi } from '@/services/api/inventory';
InventoryAdjustmentApi, import { ProductApi } from '@/services/api/master-data';
ProductWarehouseApi,
} from '@/services/api/inventory';
import { import {
CreateInventoryAdjustmentPayload, CreateInventoryAdjustmentPayload,
InventoryAdjustment, InventoryAdjustment,
@@ -28,8 +26,7 @@ import SelectInput, {
OptionType, OptionType,
useSelect, useSelect,
} from '@/components/input/SelectInput'; } 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 TextArea from '@/components/input/TextArea';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors'; 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 { ProjectFlock } from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { Kandang } from '@/types/api/master-data/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 { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import useSWR from 'swr'; import useSWR from 'swr';
@@ -46,7 +43,6 @@ import {
TRANSACTION_SUBTYPE_OPTIONS, TRANSACTION_SUBTYPE_OPTIONS,
} from '@/config/constant'; } from '@/config/constant';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
import { formatNumber } from '@/lib/helper';
interface InventoryAdjustmentFormProps { interface InventoryAdjustmentFormProps {
type?: 'add' | 'detail'; type?: 'add' | 'detail';
@@ -62,8 +58,6 @@ const InventoryAdjustmentForm = ({
InventoryAdjustmentFormErrorMessage, InventoryAdjustmentFormErrorMessage,
setInventoryAdjustmentFormErrorMessage, setInventoryAdjustmentFormErrorMessage,
] = useState(''); ] = useState('');
const [quantityLabel, setQuantityLabel] = useState('Kuantitas');
const [quantityError, setQuantityError] = useState<string>('');
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>( const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null null
@@ -188,53 +182,24 @@ const InventoryAdjustmentForm = ({
setInputValue: setProductInputValue, setInputValue: setProductInputValue,
isLoadingOptions: isLoadingProductOptions, isLoadingOptions: isLoadingProductOptions,
loadMore: loadMoreProducts, loadMore: loadMoreProducts,
rawData: productWarehouses, rawData: products,
} = useSelect<ProductWarehouse>( } = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search');
selectedKandang && selectedProjectFlockLocationId
? ProductWarehouseApi.basePath
: '',
'id',
'product.name',
'search',
{
kandang_id: selectedKandang?.value?.toString() || '',
location_id: selectedProjectFlockLocationId,
}
);
const productOptions = useMemo(() => { const productOptions = useMemo(() => {
if (!isResponseSuccess(productWarehouses)) return []; if (!isResponseSuccess(products)) return [];
const excludedFlags = ['AYAM-AFKIR', 'AYAM-CULLING', 'AYAM-MATI']; const excludedFlags = ['AYAM-AFKIR', 'AYAM-CULLING', 'AYAM-MATI'];
const filteredProducts = productWarehouses.data.filter((pw) => { const filteredProducts = products.data.filter((product) => {
const productFlags = (pw.product.flags as string[]) || []; const productFlags = (product.flags as string[]) || [];
return !productFlags.some((flag) => excludedFlags.includes(flag)); return !productFlags.some((flag) => excludedFlags.includes(flag));
}); });
return filteredProducts.map((pw) => ({ return filteredProducts.map((product) => ({
value: pw.product.id, value: product.id,
label: pw.product.name, label: product.name,
quantity: pw.quantity, flags: product.flags,
flags: pw.product.flags,
})); }));
}, [productWarehouses]); }, [products]);
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]);
const selectedProductFlags = useMemo(() => { const selectedProductFlags = useMemo(() => {
if (!selectedProduct) return []; if (!selectedProduct) return [];
@@ -244,31 +209,6 @@ const InventoryAdjustmentForm = ({
return (product?.flags as string[]) || []; return (product?.flags as string[]) || [];
}, [selectedProduct, productOptions]); }, [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(() => { const kandangOptions = useMemo(() => {
let options: OptionType[] = []; let options: OptionType[] = [];
@@ -378,42 +318,6 @@ const InventoryAdjustmentForm = ({
return transactionType === 'PEMBELIAN' || transactionType === 'PENJUALAN'; return transactionType === 'PEMBELIAN' || transactionType === 'PENJUALAN';
}, [selectedTransactionType]); }, [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(() => { useEffect(() => {
if (selectedTransactionType?.value === 'RECORDING' && selectedProduct) { if (selectedTransactionType?.value === 'RECORDING' && selectedProduct) {
setSelectedTransactionSubtype(null); setSelectedTransactionSubtype(null);
@@ -541,7 +445,6 @@ const InventoryAdjustmentForm = ({
const resetHandler = () => { const resetHandler = () => {
formik.resetForm(); formik.resetForm();
setQuantityLabel('Kuantitas');
setSelectedLocation(null); setSelectedLocation(null);
setSelectedProjectFlock(null); setSelectedProjectFlock(null);
setSelectedKandang(null); setSelectedKandang(null);
@@ -764,7 +667,6 @@ const InventoryAdjustmentForm = ({
onChange={productChangeHandler} onChange={productChangeHandler}
onInputChange={setProductInputValue} onInputChange={setProductInputValue}
options={productOptions} options={productOptions}
optionComponent={ProductOption}
onMenuScrollToBottom={loadMoreProducts} onMenuScrollToBottom={loadMoreProducts}
isLoading={isLoadingProductOptions} isLoading={isLoadingProductOptions}
isError={ isError={
@@ -777,14 +679,6 @@ const InventoryAdjustmentForm = ({
? 'Pilih Produk' ? 'Pilih Produk'
: 'Pilih Kandang terlebih dahulu' : 'Pilih Kandang terlebih dahulu'
} }
inputPrefix={
selectedProduct
? 'Stock: ' +
(productOptions.find(
(opt) => opt.value === selectedProduct?.value
)?.quantity ?? 0)
: undefined
}
isClearable isClearable
isSearchable isSearchable
/> />
@@ -841,22 +735,14 @@ const InventoryAdjustmentForm = ({
wrapper: `${selectedTransactionSubtype ? '' : 'hidden'}`, wrapper: `${selectedTransactionSubtype ? '' : 'hidden'}`,
}} }}
required required
label={quantityLabel} label='Kuantitas'
name='qty' name='qty'
value={formik.values.qty} value={formik.values.qty}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={ isError={formik.touched.qty && Boolean(formik.errors.qty)}
(formik.touched.qty && Boolean(formik.errors.qty)) || errorMessage={formik.errors.qty as string}
Boolean(quantityError)
}
errorMessage={(formik.errors.qty as string) || quantityError}
readOnly={type === 'detail'} readOnly={type === 'detail'}
bottomLabel={
selectedProduct
? `Tersedia: ${formatNumber(selectedProductQuantity)}`
: undefined
}
/> />
{/* Number Input Price */} {/* Number Input Price */}
<NumberInput <NumberInput
@@ -905,7 +791,7 @@ const InventoryAdjustmentForm = ({
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={formik.isSubmitting || Boolean(quantityError)} disabled={formik.isSubmitting}
className='px-4 w-full sm:w-auto' className='px-4 w-full sm:w-auto'
> >
Submit Submit