mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor InventoryAdjustmentForm to simplify product
handling
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user