mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE-62,65): enhance product quantity display and stock information in MovementForm
This commit is contained in:
@@ -266,6 +266,13 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
location?: string;
|
location?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProductWarehouseOptionType extends OptionType {
|
||||||
|
product_id: number;
|
||||||
|
warehouse_id: number;
|
||||||
|
warehouse_name: string;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Warehouse selection
|
// Warehouse selection
|
||||||
const [warehouseSelectInputValue, setWarehouseSelectInputValue] =
|
const [warehouseSelectInputValue, setWarehouseSelectInputValue] =
|
||||||
useState('');
|
useState('');
|
||||||
@@ -304,8 +311,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
const productWarehouseOptions = isResponseSuccess(productWarehouses)
|
const productWarehouseOptions = isResponseSuccess(productWarehouses)
|
||||||
? productWarehouses?.data.map((pw) => ({
|
? productWarehouses?.data.map((pw) => ({
|
||||||
value: pw.id,
|
value: pw.product.id,
|
||||||
label: pw.product.name,
|
label: `${pw.product.name} - ${pw.warehouse.name} (Stock: ${pw.quantity.toLocaleString('id-ID')})`,
|
||||||
product_id: pw.product.id,
|
product_id: pw.product.id,
|
||||||
warehouse_id: pw.warehouse.id,
|
warehouse_id: pw.warehouse.id,
|
||||||
warehouse_name: pw.warehouse.name,
|
warehouse_name: pw.warehouse.name,
|
||||||
@@ -438,6 +445,33 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
[productWarehouseOptions, type]
|
[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(
|
const getProductQtyError = useCallback(
|
||||||
(productIdx: number) => {
|
(productIdx: number) => {
|
||||||
if (type === 'detail') return null;
|
if (type === 'detail') return null;
|
||||||
@@ -813,7 +847,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
formik.setFieldValue(
|
formik.setFieldValue(
|
||||||
`products.${idx}.product_id`,
|
`products.${idx}.product_id`,
|
||||||
(val as OptionType)?.value
|
(val as ProductWarehouseOptionType)?.value
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
options={productWarehouseOptions}
|
options={productWarehouseOptions}
|
||||||
@@ -837,46 +871,35 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className='flex flex-col gap-2'>
|
<TextInput
|
||||||
<TextInput
|
required
|
||||||
required
|
type='number'
|
||||||
type='number'
|
name={`products.${idx}.product_qty`}
|
||||||
name={`products.${idx}.product_qty`}
|
value={product.product_qty ?? ''}
|
||||||
value={product.product_qty ?? ''}
|
onChange={formik.handleChange}
|
||||||
onChange={formik.handleChange}
|
onBlur={formik.handleBlur}
|
||||||
onBlur={formik.handleBlur}
|
endAdornment={getProductQtyAdornment(idx)}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
'products',
|
'products',
|
||||||
'product_qty',
|
'product_qty',
|
||||||
idx
|
idx
|
||||||
).isError || Boolean(getProductQtyError(idx))
|
).isError || Boolean(getProductQtyError(idx))
|
||||||
}
|
}
|
||||||
errorMessage={
|
errorMessage={
|
||||||
isRepeaterInputError(
|
isRepeaterInputError(
|
||||||
'products',
|
'products',
|
||||||
'product_qty',
|
'product_qty',
|
||||||
idx
|
idx
|
||||||
).errorMessage ||
|
).errorMessage ||
|
||||||
getProductQtyError(idx) ||
|
getProductQtyError(idx) ||
|
||||||
undefined
|
undefined
|
||||||
}
|
}
|
||||||
readOnly={type === 'detail'}
|
readOnly={type === 'detail'}
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full min-w-24',
|
wrapper: 'w-full min-w-48',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{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>
|
|
||||||
</td>
|
</td>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in New Issue
Block a user