refactor(FE-208,212): update PurchaseOrderForm and PurchaseOrderStaffApprovalForm for improved validation and dynamic item handling

This commit is contained in:
rstubryan
2025-11-18 20:43:57 +07:00
parent 0d025ba34c
commit 1b90d657ff
2 changed files with 66 additions and 112 deletions
@@ -90,15 +90,16 @@ const PurchaseStaffApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseStaffAppro
purchase_item: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
})
.nullable()
.optional(),
purchase_item_id: Yup.number()
.min(1, 'Purchase item is required!')
.required('Purchase item is required!')
.test(
'is-valid-purchase-item',
'Purchase item must be selected!',
'is-valid-purchase-item-id',
'Purchase item ID must be valid!',
function (value) {
if (!this.parent.purchase_item) return true;
return Boolean(value && value > 0);
}
)
@@ -1,6 +1,6 @@
'use client';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useFormik } from 'formik';
import { Icon } from '@iconify/react';
import { toast } from 'react-hot-toast';
@@ -8,7 +8,7 @@ import { useSearchParams } from 'next/navigation';
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 { OptionType } from '@/components/input/SelectInput';
import {
PurchaseRequestStaffApprovalFormDefaultValues,
@@ -56,7 +56,7 @@ const PurchaseOrderStaffApprovalForm = ({
// ===== UTILITY FUNCTIONS =====
const getPurchaseItemError = (
idx: number,
field: 'purchase_item_id' | 'price' | 'total_price'
field: 'price' | 'total_price'
): { isError: boolean; errorMessage: string } => {
const touchedItem = formik.touched.items?.[idx];
const errorItem = formik.errors.items?.[idx] as
@@ -135,20 +135,20 @@ const PurchaseOrderStaffApprovalForm = ({
onSubmit: async (values) => {
const payload: CreateStaffApprovalRequestPayload = {
notes: values.notes || '',
items: (values.items || []).map((item) => ({
purchase_item_id:
typeof item.purchase_item_id === 'string'
? parseInt(item.purchase_item_id) || 0
: item.purchase_item_id || 0,
items: purchaseItems.map((purchaseItem, idx) => {
const formItem = values.items?.[idx];
return {
purchase_item_id: purchaseItem.value,
price:
typeof item.price === 'string'
? parseFloat(item.price) || 0
: item.price || 0,
typeof formItem?.price === 'string'
? parseFloat(formItem.price) || 0
: formItem?.price || 0,
total_price:
typeof item.total_price === 'string'
? parseFloat(item.total_price) || 0
: item.total_price || 0,
})),
typeof formItem?.total_price === 'string'
? parseFloat(formItem.total_price) || 0
: formItem?.total_price || 0,
};
}),
};
switch (type) {
@@ -170,9 +170,9 @@ const PurchaseOrderStaffApprovalForm = ({
if (initialValues?.items) {
return initialValues.items.map((item) => ({
value: item.id,
label: `${item.product.name} ${item.quantity}`,
label: `${item.product.name} ${item.sub_qty}`,
id: item.id,
quantity: item.quantity,
quantity: item.sub_qty,
product: {
name: item.product.name,
product_category: item.product.product_category,
@@ -187,25 +187,18 @@ const PurchaseOrderStaffApprovalForm = ({
return [];
}, [initialValues?.items]);
const getPurchaseItemOptions = useCallback(() => {
return purchaseItems;
// Ensure form values are properly set when purchaseItems change
useEffect(() => {
if (purchaseItems.length > 0) {
const updatedItems = purchaseItems.map((purchaseItem) => ({
purchase_item_id: purchaseItem.value,
price: '',
total_price: '',
}));
formik.setFieldValue('items', updatedItems);
}
}, [purchaseItems]);
// ===== FIELD CHANGE HANDLERS =====
const purchaseItemChangeHandler = (
idx: number,
val: OptionType | OptionType[] | null
) => {
const purchaseItem = val as PurchaseItemOptionType | null;
formik.setFieldTouched(`items.${idx}.purchase_item`, true);
formik.setFieldValue(`items.${idx}.purchase_item`, purchaseItem);
formik.setFieldTouched(`items.${idx}.purchase_item_id`, true);
formik.setFieldValue(
`items.${idx}.purchase_item_id`,
(purchaseItem as OptionType)?.value || 0
);
};
// ===== PURCHASE ITEM OPERATIONS =====
const handlePurchaseItemChange = (
idx: number,
@@ -217,31 +210,28 @@ const PurchaseOrderStaffApprovalForm = ({
typeof value === 'string' ? parseFloat(value) || 0 : value;
formik.setFieldValue(`items.${idx}.${field}`, numValue);
if (field === 'price') {
const selectedItem = purchaseItems.find(
(p) => p.value === formik.values.items?.[idx]?.purchase_item_id
);
if (selectedItem && selectedItem.quantity && numValue > 0) {
const selectedItem = purchaseItems[idx];
if (
field === 'price' &&
selectedItem &&
selectedItem.quantity > 0 &&
numValue >= 0
) {
const calculatedTotal = numValue * selectedItem.quantity;
formik.setFieldValue(`items.${idx}.total_price`, calculatedTotal);
}
}
if (field === 'total_price') {
const selectedItem = purchaseItems.find(
(p) => p.value === formik.values.items?.[idx]?.purchase_item_id
);
if (
field === 'total_price' &&
selectedItem &&
selectedItem.quantity &&
selectedItem.quantity > 0 &&
numValue > 0
numValue >= 0
) {
const calculatedPrice = numValue / selectedItem.quantity;
formik.setFieldValue(`items.${idx}.price`, calculatedPrice);
}
}
}
};
return (
@@ -260,10 +250,6 @@ const PurchaseOrderStaffApprovalForm = ({
<table className='table'>
<thead>
<tr>
<th>
Item
<span className='text-error'>*</span>
</th>
<th>Gudang</th>
<th>Produk</th>
<th>Jenis Produk</th>
@@ -280,43 +266,16 @@ const PurchaseOrderStaffApprovalForm = ({
</tr>
</thead>
<tbody>
{formik.values.items?.map((item, idx) => {
const selectedPurchaseItem = purchaseItems.find(
(p) => p.value === item.purchase_item_id
);
{purchaseItems?.map((purchaseItem, idx) => {
const formItem = formik.values.items?.[idx];
return (
<tr key={`purchase-item-${idx}`}>
<td>
<SelectInput
required
isClearable={true}
value={item.purchase_item}
key={`purchase-item-${idx}`}
onChange={(val) =>
purchaseItemChangeHandler(idx, val)
}
options={getPurchaseItemOptions()}
isError={
getPurchaseItemError(idx, 'purchase_item_id')
.isError
}
errorMessage={
getPurchaseItemError(idx, 'purchase_item_id')
.errorMessage
}
placeholder='Pilih Item...'
className={{
wrapper: 'min-w-52 md:min-w-72 lg:min-w-80',
}}
/>
</td>
<td>
<TextInput
name={`items.${idx}.warehouse`}
type='text'
value={selectedPurchaseItem?.warehouse?.name || ''}
value={purchaseItem?.warehouse?.name || ''}
readOnly={true}
placeholder='Pilih item terlebih dahulu'
className={{
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
}}
@@ -327,9 +286,8 @@ const PurchaseOrderStaffApprovalForm = ({
<TextInput
name={`items.${idx}.product_name`}
type='text'
value={selectedPurchaseItem?.product?.name || ''}
value={purchaseItem?.product?.name || ''}
readOnly={true}
placeholder='Pilih item terlebih dahulu'
className={{
wrapper: 'min-w-52 md:min-w-72 lg:min-w-80',
}}
@@ -341,14 +299,13 @@ const PurchaseOrderStaffApprovalForm = ({
name={`items.${idx}.product_category`}
type='text'
value={
typeof selectedPurchaseItem?.product
?.product_category === 'string'
? selectedPurchaseItem.product.product_category
: selectedPurchaseItem?.product?.product_category
?.name || ''
typeof purchaseItem?.product?.product_category ===
'string'
? purchaseItem.product.product_category
: purchaseItem?.product?.product_category?.name ||
''
}
readOnly={true}
placeholder='Pilih item terlebih dahulu'
className={{
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
}}
@@ -360,14 +317,11 @@ const PurchaseOrderStaffApprovalForm = ({
name={`items.${idx}.quantity`}
type='text'
value={
selectedPurchaseItem?.quantity
? selectedPurchaseItem.quantity.toLocaleString(
'id-ID'
)
purchaseItem?.quantity
? purchaseItem.quantity.toLocaleString('id-ID')
: ''
}
readOnly={true}
placeholder='Pilih item terlebih dahulu'
className={{
wrapper: 'min-w-24',
}}
@@ -378,9 +332,8 @@ const PurchaseOrderStaffApprovalForm = ({
<TextInput
name={`items.${idx}.uom`}
type='text'
value={selectedPurchaseItem?.product?.uom?.name || ''}
value={purchaseItem?.product?.uom?.name || ''}
readOnly={true}
placeholder='Pilih item terlebih dahulu'
className={{
wrapper: 'min-w-24',
}}
@@ -391,7 +344,7 @@ const PurchaseOrderStaffApprovalForm = ({
<NumberInput
required
name={`items.${idx}.price`}
value={item.price || ''}
value={formItem?.price || ''}
onChange={(e) =>
handlePurchaseItemChange(
idx,
@@ -419,7 +372,7 @@ const PurchaseOrderStaffApprovalForm = ({
<NumberInput
required
name={`items.${idx}.total_price`}
value={item.total_price || ''}
value={formItem?.total_price || ''}
onChange={(e) =>
handlePurchaseItemChange(
idx,