mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-114,137): implement stock validation in RecordingForm to manage usage limits and enhance user feedback
This commit is contained in:
@@ -6,7 +6,6 @@ import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
@@ -265,6 +264,79 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
manuallyEditedRows, // Include manually edited rows in dependencies
|
||||
]);
|
||||
|
||||
// Stock validation functions - Following MovementForm pattern
|
||||
const getAvailableStock = useCallback(
|
||||
(productWarehouseId: number) => {
|
||||
if (type === 'detail') return 0;
|
||||
|
||||
if (!isResponseSuccess(stockProducts)) return 0;
|
||||
|
||||
const productWarehouse = stockProducts.data.find(
|
||||
(pw) => pw.id === productWarehouseId
|
||||
);
|
||||
|
||||
return productWarehouse?.quantity ?? 0;
|
||||
},
|
||||
[stockProducts, type]
|
||||
);
|
||||
|
||||
const getStockUsageError = useCallback(
|
||||
(stockIdx: number) => {
|
||||
if (type === 'detail') return null;
|
||||
|
||||
const stock = formik.values.stocks?.[stockIdx];
|
||||
if (!stock || !stock.product_warehouse_id) return null;
|
||||
|
||||
const availableStock = getAvailableStock(stock.product_warehouse_id);
|
||||
const requestedUsage = Number(stock.usage_amount) || 0;
|
||||
|
||||
if (requestedUsage > availableStock) {
|
||||
return `Jumlah pakai melebihi stok tersedia! Maksimal: ${availableStock.toLocaleString('id-ID')}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[formik.values.stocks, getAvailableStock, type]
|
||||
);
|
||||
|
||||
const getStockUsageAdornment = useCallback(
|
||||
(stockIdx: number) => {
|
||||
if (type === 'detail') return null;
|
||||
|
||||
const stock = formik.values.stocks?.[stockIdx];
|
||||
if (!stock || !stock.product_warehouse_id) return null;
|
||||
|
||||
const availableStock = getAvailableStock(stock.product_warehouse_id);
|
||||
const requestedUsage = Number(stock.usage_amount) || 0;
|
||||
const remainingStock = availableStock - requestedUsage;
|
||||
|
||||
if (requestedUsage > 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.stocks, getAvailableStock, type]
|
||||
);
|
||||
|
||||
const hasExceededStock = useMemo(() => {
|
||||
if (type === 'detail') return false;
|
||||
|
||||
return (
|
||||
formik.values.stocks?.some((stock, idx) => {
|
||||
return getStockUsageError(idx) !== null;
|
||||
}) ?? false
|
||||
);
|
||||
}, [formik.values.stocks, getStockUsageError, type]);
|
||||
|
||||
// EVENT HANDLERS - Body Weights
|
||||
const addBodyWeight = () => {
|
||||
const newBodyWeights = [
|
||||
@@ -440,7 +512,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
}, [stockProducts, getProjectFlockLocation()]);
|
||||
|
||||
|
||||
// Handle stock usage amount change
|
||||
// Handle stock usage amount change - simplified following MovementForm pattern
|
||||
const handleStockUsageAmountChangeWrapper = useCallback(
|
||||
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseInt(e.target.value.replace(/[^\d.-]/g, '')) || 0;
|
||||
@@ -920,6 +992,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
`stocks.${idx}.product_warehouse_id`,
|
||||
option?.value || 0
|
||||
);
|
||||
|
||||
// Auto-populate notes with product name by finding it in stockProducts data
|
||||
if (option?.value && isResponseSuccess(stockProducts)) {
|
||||
const selectedProduct = stockProducts.data.find(
|
||||
@@ -957,6 +1030,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex flex-col gap-1">
|
||||
<NumberInput
|
||||
required
|
||||
name={`stocks.${idx}.usage_amount`}
|
||||
@@ -970,18 +1044,22 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
decimalSeparator=''
|
||||
isError={
|
||||
isRepeaterInputError('stocks', 'usage_amount', idx)
|
||||
.isError
|
||||
.isError || Boolean(getStockUsageError(idx))
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError('stocks', 'usage_amount', idx)
|
||||
.errorMessage
|
||||
.errorMessage ||
|
||||
getStockUsageError(idx) ||
|
||||
undefined
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
placeholder='Jumlah Pakai'
|
||||
placeholder="Jumlah Pakai"
|
||||
/>
|
||||
{type !== 'detail' && getStockUsageAdornment(idx)}
|
||||
</div>
|
||||
</td>
|
||||
{type !== 'detail' && (
|
||||
<td>
|
||||
@@ -1226,6 +1304,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
: undefined
|
||||
}
|
||||
onDelete={deleteRecordingClickHandler}
|
||||
disableSubmit={hasExceededStock}
|
||||
/>
|
||||
{recordingFormErrorMessage && (
|
||||
<div role='alert' className='alert alert-error'>
|
||||
|
||||
Reference in New Issue
Block a user