mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 23:35:45 +00:00
refactor(FE-208,212): update PurchaseOrderForm and PurchaseOrderStaffApprovalForm for improved validation and dynamic item handling
This commit is contained in:
@@ -90,15 +90,16 @@ const PurchaseStaffApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseStaffAppro
|
|||||||
purchase_item: Yup.object({
|
purchase_item: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
purchase_item_id: Yup.number()
|
purchase_item_id: Yup.number()
|
||||||
.min(1, 'Purchase item is required!')
|
.min(1, 'Purchase item is required!')
|
||||||
.required('Purchase item is required!')
|
.required('Purchase item is required!')
|
||||||
.test(
|
.test(
|
||||||
'is-valid-purchase-item',
|
'is-valid-purchase-item-id',
|
||||||
'Purchase item must be selected!',
|
'Purchase item ID must be valid!',
|
||||||
function (value) {
|
function (value) {
|
||||||
if (!this.parent.purchase_item) return true;
|
|
||||||
return Boolean(value && value > 0);
|
return Boolean(value && value > 0);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
@@ -8,7 +8,7 @@ import { useSearchParams } from 'next/navigation';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import NumberInput from '@/components/input/NumberInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PurchaseRequestStaffApprovalFormDefaultValues,
|
PurchaseRequestStaffApprovalFormDefaultValues,
|
||||||
@@ -56,7 +56,7 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
// ===== UTILITY FUNCTIONS =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
const getPurchaseItemError = (
|
const getPurchaseItemError = (
|
||||||
idx: number,
|
idx: number,
|
||||||
field: 'purchase_item_id' | 'price' | 'total_price'
|
field: 'price' | 'total_price'
|
||||||
): { isError: boolean; errorMessage: string } => {
|
): { isError: boolean; errorMessage: string } => {
|
||||||
const touchedItem = formik.touched.items?.[idx];
|
const touchedItem = formik.touched.items?.[idx];
|
||||||
const errorItem = formik.errors.items?.[idx] as
|
const errorItem = formik.errors.items?.[idx] as
|
||||||
@@ -135,20 +135,20 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
const payload: CreateStaffApprovalRequestPayload = {
|
const payload: CreateStaffApprovalRequestPayload = {
|
||||||
notes: values.notes || '',
|
notes: values.notes || '',
|
||||||
items: (values.items || []).map((item) => ({
|
items: purchaseItems.map((purchaseItem, idx) => {
|
||||||
purchase_item_id:
|
const formItem = values.items?.[idx];
|
||||||
typeof item.purchase_item_id === 'string'
|
return {
|
||||||
? parseInt(item.purchase_item_id) || 0
|
purchase_item_id: purchaseItem.value,
|
||||||
: item.purchase_item_id || 0,
|
price:
|
||||||
price:
|
typeof formItem?.price === 'string'
|
||||||
typeof item.price === 'string'
|
? parseFloat(formItem.price) || 0
|
||||||
? parseFloat(item.price) || 0
|
: formItem?.price || 0,
|
||||||
: item.price || 0,
|
total_price:
|
||||||
total_price:
|
typeof formItem?.total_price === 'string'
|
||||||
typeof item.total_price === 'string'
|
? parseFloat(formItem.total_price) || 0
|
||||||
? parseFloat(item.total_price) || 0
|
: formItem?.total_price || 0,
|
||||||
: item.total_price || 0,
|
};
|
||||||
})),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -170,9 +170,9 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
if (initialValues?.items) {
|
if (initialValues?.items) {
|
||||||
return initialValues.items.map((item) => ({
|
return initialValues.items.map((item) => ({
|
||||||
value: item.id,
|
value: item.id,
|
||||||
label: `${item.product.name} ${item.quantity}`,
|
label: `${item.product.name} ${item.sub_qty}`,
|
||||||
id: item.id,
|
id: item.id,
|
||||||
quantity: item.quantity,
|
quantity: item.sub_qty,
|
||||||
product: {
|
product: {
|
||||||
name: item.product.name,
|
name: item.product.name,
|
||||||
product_category: item.product.product_category,
|
product_category: item.product.product_category,
|
||||||
@@ -187,25 +187,18 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
return [];
|
return [];
|
||||||
}, [initialValues?.items]);
|
}, [initialValues?.items]);
|
||||||
|
|
||||||
const getPurchaseItemOptions = useCallback(() => {
|
// Ensure form values are properly set when purchaseItems change
|
||||||
return purchaseItems;
|
useEffect(() => {
|
||||||
|
if (purchaseItems.length > 0) {
|
||||||
|
const updatedItems = purchaseItems.map((purchaseItem) => ({
|
||||||
|
purchase_item_id: purchaseItem.value,
|
||||||
|
price: '',
|
||||||
|
total_price: '',
|
||||||
|
}));
|
||||||
|
formik.setFieldValue('items', updatedItems);
|
||||||
|
}
|
||||||
}, [purchaseItems]);
|
}, [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 =====
|
// ===== PURCHASE ITEM OPERATIONS =====
|
||||||
const handlePurchaseItemChange = (
|
const handlePurchaseItemChange = (
|
||||||
idx: number,
|
idx: number,
|
||||||
@@ -217,29 +210,26 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
typeof value === 'string' ? parseFloat(value) || 0 : value;
|
typeof value === 'string' ? parseFloat(value) || 0 : value;
|
||||||
formik.setFieldValue(`items.${idx}.${field}`, numValue);
|
formik.setFieldValue(`items.${idx}.${field}`, numValue);
|
||||||
|
|
||||||
if (field === 'price') {
|
const selectedItem = purchaseItems[idx];
|
||||||
const selectedItem = purchaseItems.find(
|
|
||||||
(p) => p.value === formik.values.items?.[idx]?.purchase_item_id
|
if (
|
||||||
);
|
field === 'price' &&
|
||||||
if (selectedItem && selectedItem.quantity && numValue > 0) {
|
selectedItem &&
|
||||||
const calculatedTotal = numValue * selectedItem.quantity;
|
selectedItem.quantity > 0 &&
|
||||||
formik.setFieldValue(`items.${idx}.total_price`, calculatedTotal);
|
numValue >= 0
|
||||||
}
|
) {
|
||||||
|
const calculatedTotal = numValue * selectedItem.quantity;
|
||||||
|
formik.setFieldValue(`items.${idx}.total_price`, calculatedTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === 'total_price') {
|
if (
|
||||||
const selectedItem = purchaseItems.find(
|
field === 'total_price' &&
|
||||||
(p) => p.value === formik.values.items?.[idx]?.purchase_item_id
|
selectedItem &&
|
||||||
);
|
selectedItem.quantity > 0 &&
|
||||||
if (
|
numValue >= 0
|
||||||
selectedItem &&
|
) {
|
||||||
selectedItem.quantity &&
|
const calculatedPrice = numValue / selectedItem.quantity;
|
||||||
selectedItem.quantity > 0 &&
|
formik.setFieldValue(`items.${idx}.price`, calculatedPrice);
|
||||||
numValue > 0
|
|
||||||
) {
|
|
||||||
const calculatedPrice = numValue / selectedItem.quantity;
|
|
||||||
formik.setFieldValue(`items.${idx}.price`, calculatedPrice);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -260,10 +250,6 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
<table className='table'>
|
<table className='table'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
|
||||||
Item
|
|
||||||
<span className='text-error'>*</span>
|
|
||||||
</th>
|
|
||||||
<th>Gudang</th>
|
<th>Gudang</th>
|
||||||
<th>Produk</th>
|
<th>Produk</th>
|
||||||
<th>Jenis Produk</th>
|
<th>Jenis Produk</th>
|
||||||
@@ -280,43 +266,16 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{formik.values.items?.map((item, idx) => {
|
{purchaseItems?.map((purchaseItem, idx) => {
|
||||||
const selectedPurchaseItem = purchaseItems.find(
|
const formItem = formik.values.items?.[idx];
|
||||||
(p) => p.value === item.purchase_item_id
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<tr key={`purchase-item-${idx}`}>
|
<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>
|
<td>
|
||||||
<TextInput
|
<TextInput
|
||||||
name={`items.${idx}.warehouse`}
|
name={`items.${idx}.warehouse`}
|
||||||
type='text'
|
type='text'
|
||||||
value={selectedPurchaseItem?.warehouse?.name || ''}
|
value={purchaseItem?.warehouse?.name || ''}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
placeholder='Pilih item terlebih dahulu'
|
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
|
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
|
||||||
}}
|
}}
|
||||||
@@ -327,9 +286,8 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
<TextInput
|
<TextInput
|
||||||
name={`items.${idx}.product_name`}
|
name={`items.${idx}.product_name`}
|
||||||
type='text'
|
type='text'
|
||||||
value={selectedPurchaseItem?.product?.name || ''}
|
value={purchaseItem?.product?.name || ''}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
placeholder='Pilih item terlebih dahulu'
|
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'min-w-52 md:min-w-72 lg:min-w-80',
|
wrapper: 'min-w-52 md:min-w-72 lg:min-w-80',
|
||||||
}}
|
}}
|
||||||
@@ -341,14 +299,13 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
name={`items.${idx}.product_category`}
|
name={`items.${idx}.product_category`}
|
||||||
type='text'
|
type='text'
|
||||||
value={
|
value={
|
||||||
typeof selectedPurchaseItem?.product
|
typeof purchaseItem?.product?.product_category ===
|
||||||
?.product_category === 'string'
|
'string'
|
||||||
? selectedPurchaseItem.product.product_category
|
? purchaseItem.product.product_category
|
||||||
: selectedPurchaseItem?.product?.product_category
|
: purchaseItem?.product?.product_category?.name ||
|
||||||
?.name || ''
|
''
|
||||||
}
|
}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
placeholder='Pilih item terlebih dahulu'
|
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
|
wrapper: 'min-w-40 md:min-w-52 lg:min-w-64',
|
||||||
}}
|
}}
|
||||||
@@ -360,14 +317,11 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
name={`items.${idx}.quantity`}
|
name={`items.${idx}.quantity`}
|
||||||
type='text'
|
type='text'
|
||||||
value={
|
value={
|
||||||
selectedPurchaseItem?.quantity
|
purchaseItem?.quantity
|
||||||
? selectedPurchaseItem.quantity.toLocaleString(
|
? purchaseItem.quantity.toLocaleString('id-ID')
|
||||||
'id-ID'
|
|
||||||
)
|
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
placeholder='Pilih item terlebih dahulu'
|
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'min-w-24',
|
wrapper: 'min-w-24',
|
||||||
}}
|
}}
|
||||||
@@ -378,9 +332,8 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
<TextInput
|
<TextInput
|
||||||
name={`items.${idx}.uom`}
|
name={`items.${idx}.uom`}
|
||||||
type='text'
|
type='text'
|
||||||
value={selectedPurchaseItem?.product?.uom?.name || ''}
|
value={purchaseItem?.product?.uom?.name || ''}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
placeholder='Pilih item terlebih dahulu'
|
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'min-w-24',
|
wrapper: 'min-w-24',
|
||||||
}}
|
}}
|
||||||
@@ -391,7 +344,7 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`items.${idx}.price`}
|
name={`items.${idx}.price`}
|
||||||
value={item.price || ''}
|
value={formItem?.price || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handlePurchaseItemChange(
|
handlePurchaseItemChange(
|
||||||
idx,
|
idx,
|
||||||
@@ -419,7 +372,7 @@ const PurchaseOrderStaffApprovalForm = ({
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`items.${idx}.total_price`}
|
name={`items.${idx}.total_price`}
|
||||||
value={item.total_price || ''}
|
value={formItem?.total_price || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handlePurchaseItemChange(
|
handlePurchaseItemChange(
|
||||||
idx,
|
idx,
|
||||||
|
|||||||
Reference in New Issue
Block a user