mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-114): add average weight calculation and input handling in RecordingForm
This commit is contained in:
@@ -39,6 +39,11 @@ export const RecordingFormSchema = Yup.object({
|
||||
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
||||
.typeError('Jumlah ayam harus berupa angka!')
|
||||
.default(1),
|
||||
average_weight: Yup.number()
|
||||
.optional()
|
||||
.min(0, 'Rata-rata berat tidak boleh negatif!')
|
||||
.typeError('Rata-rata berat harus berupa angka!')
|
||||
.default(0),
|
||||
notes: Yup.string().optional(),
|
||||
})
|
||||
)
|
||||
@@ -123,12 +128,14 @@ export const getRecordingFormInitialValues = (
|
||||
(bw: NonNullable<CreateRecordingPayload['body_weights']>[0]) => ({
|
||||
weight: bw.weight,
|
||||
qty: bw.qty,
|
||||
average_weight: bw.qty > 0 ? Math.round(bw.weight / bw.qty) : 0,
|
||||
notes: bw.notes || '',
|
||||
})
|
||||
) ?? [
|
||||
{
|
||||
weight: 0,
|
||||
qty: 1,
|
||||
average_weight: 0,
|
||||
notes: '',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
@@ -135,6 +136,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
? bw.qty
|
||||
: parseFloat(String(bw.qty)) || 0,
|
||||
notes: bw.notes,
|
||||
// average_weight is not included in payload as it's calculated field only
|
||||
})),
|
||||
stocks: (values.stocks ?? []).map((stock) => ({
|
||||
product_warehouse_id: stock.product_warehouse_id,
|
||||
@@ -229,6 +231,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
}
|
||||
}, [formik.values.record_datetime]);
|
||||
|
||||
// Auto-calculate average weight when weight or qty changes
|
||||
useEffect(() => {
|
||||
if (formik.values.body_weights) {
|
||||
const updatedBodyWeights = formik.values.body_weights.map((weight) => ({
|
||||
...weight,
|
||||
average_weight:
|
||||
weight.qty > 0 && weight.weight > 0
|
||||
? Math.round(weight.weight / weight.qty)
|
||||
: 0,
|
||||
}));
|
||||
|
||||
// Only update if values are different to avoid infinite loops
|
||||
const hasChanges = updatedBodyWeights.some(
|
||||
(updated, idx) =>
|
||||
updated.average_weight !==
|
||||
(formik.values.body_weights[idx]?.average_weight || 0)
|
||||
);
|
||||
|
||||
if (hasChanges) {
|
||||
formik.setFieldValue('body_weights', updatedBodyWeights, false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
formik.values.body_weights?.map((w) => w.weight),
|
||||
formik.values.body_weights?.map((w) => w.qty),
|
||||
]);
|
||||
|
||||
// EVENT HANDLERS - Body Weights
|
||||
const addBodyWeight = () => {
|
||||
const newBodyWeights = [
|
||||
@@ -237,11 +266,76 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
weight: 0,
|
||||
qty: 1,
|
||||
notes: '',
|
||||
average_weight: 0,
|
||||
},
|
||||
];
|
||||
formik.setFieldValue('body_weights', newBodyWeights);
|
||||
};
|
||||
|
||||
// Handle calculation when weight changes
|
||||
const handleWeightChange = (idx: number, value: number) => {
|
||||
formik.setFieldValue(`body_weights.${idx}.weight`, value);
|
||||
|
||||
const currentWeight = formik.values.body_weights?.[idx];
|
||||
if (currentWeight) {
|
||||
const qty = currentWeight.qty;
|
||||
if (qty > 0 && value > 0) {
|
||||
const averageWeight = Math.round(value / qty);
|
||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, averageWeight);
|
||||
} else {
|
||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle calculation when qty changes
|
||||
const handleQtyChange = (idx: number, value: number) => {
|
||||
formik.setFieldValue(`body_weights.${idx}.qty`, value);
|
||||
|
||||
const currentWeight = formik.values.body_weights?.[idx];
|
||||
if (currentWeight) {
|
||||
const weight = currentWeight.weight;
|
||||
if (value > 0 && weight > 0) {
|
||||
const averageWeight = Math.round(weight / value);
|
||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, averageWeight);
|
||||
} else {
|
||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle calculation when average_weight changes
|
||||
const handleAverageWeightChange = (idx: number, value: number) => {
|
||||
formik.setFieldValue(`body_weights.${idx}.average_weight`, value);
|
||||
|
||||
const currentWeight = formik.values.body_weights?.[idx];
|
||||
if (currentWeight) {
|
||||
const qty = currentWeight.qty;
|
||||
if (qty > 0 && value > 0) {
|
||||
const totalWeight = value * qty;
|
||||
formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight);
|
||||
} else {
|
||||
formik.setFieldValue(`body_weights.${idx}.weight`, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create wrapper handlers that match NumberInput's onChange signature
|
||||
const handleWeightChangeWrapper = (idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0;
|
||||
handleWeightChange(idx, value);
|
||||
};
|
||||
|
||||
const handleQtyChangeWrapper = (idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0;
|
||||
handleQtyChange(idx, value);
|
||||
};
|
||||
|
||||
const handleAverageWeightChangeWrapper = (idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0;
|
||||
handleAverageWeightChange(idx, value);
|
||||
};
|
||||
|
||||
const removeBodyWeight = (idx: number) => {
|
||||
const updatedBodyWeights = formik.values.body_weights?.filter(
|
||||
(_, i) => i !== idx
|
||||
@@ -353,6 +447,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='w-full'>
|
||||
@@ -514,6 +609,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
Rata-rata Berat Ayam (gram)
|
||||
<span
|
||||
className='tooltip tooltip-info tooltip-bottom z-[9999]'
|
||||
data-tip='Otomatis dihitung: Total Berat ÷ Jumlah Ayam'
|
||||
>
|
||||
<Icon icon="material-symbols:info-outline" width={16} height={16} />
|
||||
</span>
|
||||
</th>
|
||||
<th>Catatan</th>
|
||||
{type !== 'detail' && <th>Action</th>}
|
||||
</tr>
|
||||
@@ -546,17 +650,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
</td>
|
||||
)}
|
||||
<td>
|
||||
<TextInput
|
||||
<NumberInput
|
||||
required
|
||||
name={`body_weights.${idx}.weight`}
|
||||
type='number'
|
||||
value={
|
||||
typeof bw.weight === 'number'
|
||||
? bw.weight.toString()
|
||||
: bw.weight
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
value={bw.weight}
|
||||
onChange={handleWeightChangeWrapper(idx)}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='weight'
|
||||
weightUnit='gram'
|
||||
decimals={2}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('body_weights', 'weight', idx)
|
||||
.isError
|
||||
@@ -569,21 +674,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
}}
|
||||
placeholder='Berat ayam (gram)'
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
<NumberInput
|
||||
required
|
||||
name={`body_weights.${idx}.qty`}
|
||||
type='number'
|
||||
value={
|
||||
typeof bw.qty === 'number'
|
||||
? bw.qty.toString()
|
||||
: bw.qty
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
value={bw.qty}
|
||||
onChange={handleQtyChangeWrapper(idx)}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='number'
|
||||
decimals={0}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('body_weights', 'qty', idx)
|
||||
.isError
|
||||
@@ -596,7 +700,32 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
className={{
|
||||
wrapper: 'w-full min-w-24',
|
||||
}}
|
||||
placeholder='Jumlah ayam'
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<NumberInput
|
||||
name={`body_weights.${idx}.average_weight`}
|
||||
value={bw.average_weight || 0}
|
||||
onChange={handleAverageWeightChangeWrapper(idx)}
|
||||
onBlur={formik.handleBlur}
|
||||
maskType='weight'
|
||||
weightUnit='gram'
|
||||
decimals={2}
|
||||
min={0}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
isError={
|
||||
isRepeaterInputError('body_weights', 'average_weight', idx)
|
||||
.isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError('body_weights', 'average_weight', idx)
|
||||
.errorMessage
|
||||
}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'w-full min-w-32',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Reference in New Issue
Block a user