refactor(FE-62,65): enhance product quantity display and stock information in MovementForm

This commit is contained in:
rstubryan
2025-10-17 19:23:19 +07:00
parent f05d367a5d
commit 0676411dd5
@@ -266,6 +266,13 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
location?: string;
}
interface ProductWarehouseOptionType extends OptionType {
product_id: number;
warehouse_id: number;
warehouse_name: string;
quantity: number;
}
// Warehouse selection
const [warehouseSelectInputValue, setWarehouseSelectInputValue] =
useState('');
@@ -304,8 +311,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
);
const productWarehouseOptions = isResponseSuccess(productWarehouses)
? productWarehouses?.data.map((pw) => ({
value: pw.id,
label: pw.product.name,
value: pw.product.id,
label: `${pw.product.name} - ${pw.warehouse.name} (Stock: ${pw.quantity.toLocaleString('id-ID')})`,
product_id: pw.product.id,
warehouse_id: pw.warehouse.id,
warehouse_name: pw.warehouse.name,
@@ -438,6 +445,33 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
[productWarehouseOptions, type]
);
const getProductQtyAdornment = useCallback(
(productIdx: number) => {
if (type === 'detail') return null;
const product = formik.values.products?.[productIdx];
if (!product || !product.product_id) return null;
const availableStock = getAvailableStock(product.product_id);
const requestedQty = Number(product.product_qty) || 0;
const remainingStock = availableStock - requestedQty;
if (requestedQty > 0) {
return (
<span className='text-sm text-gray-600 whitespace-nowrap'>
(sisa: {remainingStock.toLocaleString('id-ID')})
</span>
);
}
return (
<span className='text-sm text-gray-600 whitespace-nowrap'>
(tersedia: {availableStock.toLocaleString('id-ID')})
</span>
);
},
[formik.values.products, getAvailableStock, type]
);
const getProductQtyError = useCallback(
(productIdx: number) => {
if (type === 'detail') return null;
@@ -813,7 +847,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
);
formik.setFieldValue(
`products.${idx}.product_id`,
(val as OptionType)?.value
(val as ProductWarehouseOptionType)?.value
);
}}
options={productWarehouseOptions}
@@ -837,46 +871,35 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
/>
</td>
<td>
<div className='flex flex-col gap-2'>
<TextInput
required
type='number'
name={`products.${idx}.product_qty`}
value={product.product_qty ?? ''}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
isError={
isRepeaterInputError(
'products',
'product_qty',
idx
).isError || Boolean(getProductQtyError(idx))
}
errorMessage={
isRepeaterInputError(
'products',
'product_qty',
idx
).errorMessage ||
getProductQtyError(idx) ||
undefined
}
readOnly={type === 'detail'}
className={{
wrapper: 'w-full min-w-24',
}}
/>
{type !== 'detail' && product.product_id && (
<div className='text-sm text-gray-600'>
<span className='font-semibold'>
Stok tersedia:
</span>{' '}
{getAvailableStock(
product.product_id
).toLocaleString('id-ID')}
</div>
)}
</div>
<TextInput
required
type='number'
name={`products.${idx}.product_qty`}
value={product.product_qty ?? ''}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
endAdornment={getProductQtyAdornment(idx)}
isError={
isRepeaterInputError(
'products',
'product_qty',
idx
).isError || Boolean(getProductQtyError(idx))
}
errorMessage={
isRepeaterInputError(
'products',
'product_qty',
idx
).errorMessage ||
getProductQtyError(idx) ||
undefined
}
readOnly={type === 'detail'}
className={{
wrapper: 'w-full min-w-48',
}}
/>
</td>
{type !== 'detail' && (
<td>