fix(FE-179-220): Adjust form add and edit delivery, add validation to prevent duplicate product

This commit is contained in:
randy-ar
2025-11-22 12:04:46 +07:00
parent b198f24b75
commit eaaed9521b
10 changed files with 431 additions and 271 deletions
@@ -42,22 +42,10 @@ export const DeliveryOrderProductSchema: Yup.ObjectSchema<DeliveryOrderProductSc
.min(1, 'Total Penjualan wajib diisi!')
.required('Total Penjualan wajib diisi!'),
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
delivery_date: Yup.string()
.required('Tanggal Pengiriman wajib diisi!')
.nullable()
.optional(),
delivery_date: Yup.string().required('Tanggal Pengiriman wajib diisi!'),
do_number: Yup.string().nullable().optional(),
});
export type DeliveryOrderProductFormValues = Yup.InferType<
typeof DeliveryOrderProductSchema
>;
// "marketing_product_id": 3,
// "qty": 20,
// "unit_price": 1000,
// "avg_weight": 1.1,
// "total_weight": 220,
// "total_price": 20000,
// "delivery_date": "2025-11-09",
// "vehicle_number": "D 4321 XXX"
@@ -15,15 +15,20 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
import { SalesOrderProductFormValues } from '../sales-order/SalesOrderProduct.schema';
import { BaseSalesOrder } from '@/types/api/marketing/marketing';
import Badge from '@/components/Badge';
import { SalesProductToFieldValues } from '../../MarketingForm';
const DeliveryOrderProductForm = ({
formState,
salesOrders,
initialValues,
exisitingValues,
onSubmitForm,
onUpdateForm,
}: {
formState: 'add' | 'edit';
salesOrders: BaseSalesOrder[];
initialValues?: DeliveryOrderProductFormValues;
exisitingValues?: DeliveryOrderProductFormValues[];
onSubmitForm?: (value: DeliveryOrderProductFormValues) => Promise<void>;
onUpdateForm?: (
id: number,
@@ -34,6 +39,7 @@ const DeliveryOrderProductForm = ({
const [selectedProduct, setSelectedProduct] = useState<OptionType | null>(
null
);
const [currentInput, setCurrentInput] = useState<string>('');
const formik = useFormik<DeliveryOrderProductFormValues>({
enableReinitialize: true,
@@ -48,15 +54,15 @@ const DeliveryOrderProductForm = ({
total_price: initialValues?.total_price || undefined,
marketing_product: initialValues?.marketing_product || undefined,
},
isInitialValid: false,
validationSchema: DeliveryOrderProductSchema,
validateOnBlur: true,
validateOnChange: false,
onSubmit: async (values) => {
setFormErrorMessage('');
if (initialValues?.id) {
await onUpdateForm?.(initialValues.id, values);
} else {
await onSubmitForm?.(values);
await onUpdateForm?.(values.marketing_product_id as number, values);
}
handleResetForm();
},
@@ -81,6 +87,7 @@ const DeliveryOrderProductForm = ({
};
const handleBlurField = (field: string) => {
setCurrentInput(field);
const { qty, unit_price, total_price, avg_weight, total_weight } =
formik.values;
@@ -101,47 +108,37 @@ const DeliveryOrderProductForm = ({
}
};
const MarketingProductToFieldValues = (
product: BaseSalesOrder
): SalesOrderProductFormValues => {
return {
id: product.id,
vehicle_number: product.vehicle_number,
kandang_id: product.product_warehouse.warehouse.id,
kandang: {
value: product.product_warehouse.warehouse.id,
label: product.product_warehouse.warehouse.name,
},
product_warehouse: {
value: product.product_warehouse.id,
label: product.product_warehouse.product.name,
},
product_warehouse_id: product.product_warehouse.id,
unit_price: product.unit_price,
total_weight: product.total_weight,
qty: product.qty,
avg_weight: product.avg_weight,
total_price: product.total_price,
};
};
const options = salesOrders.map((item) => ({
value: item.id,
label: `${item.product_warehouse.product.name} - ${item.product_warehouse.warehouse.name}`,
}));
const options = exisitingValues
?.map((item) => {
if (!Boolean(item.qty)) {
return {
value: item.id,
label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.kandang?.label}`,
} as OptionType;
} else {
return null;
}
})
?.filter((item) => item != null) as OptionType[];
const { setValues: setFormikValues } = formik;
useEffect(() => {
if (initialValues) {
setFormikValues(initialValues);
const value = salesOrders.find(
(item) => item.id === initialValues.marketing_product_id
);
setSelectedProduct({
value: value?.id,
label: `${value?.product_warehouse.product.name} - ${value?.product_warehouse.warehouse.name}`,
} as OptionType);
if (!Boolean(initialValues.qty)) {
handleResetForm();
} else {
setFormikValues(initialValues);
// const value = exisitingValues?.find(
// (item) => item.id === initialValues?.id
// );
if (initialValues?.marketing_product_id) {
setSelectedProduct({
value: initialValues?.id,
label: `${initialValues?.marketing_product?.product_warehouse?.label} - ${initialValues?.marketing_product?.kandang?.label}`,
} as OptionType);
}
}
}
}, [initialValues]);
@@ -149,17 +146,21 @@ const DeliveryOrderProductForm = ({
<>
<form
className='size-full'
onSubmit={formik.handleSubmit}
onSubmit={(e) => {
e.preventDefault();
handleBlurField(currentInput);
formik.handleSubmit(e);
}}
onReset={handleResetForm}
>
{/* <small className='block text-blue-500'>
{JSON.stringify(initialValues)}
</small>
<small className='block text-red-500'>
{JSON.stringify(formik.errors)}
{JSON.stringify(exisitingValues)}
</small>
<small className='block text-emerald-500'>
{JSON.stringify(formik.values)}
</small> */}
{/* <small className='block text-red-500'>
{JSON.stringify(formik.errors)}
</small>
<div className='hidden'>
{JSON.stringify(formik.values.marketing_product)}
@@ -176,14 +177,14 @@ const DeliveryOrderProductForm = ({
options={options}
label='Produk'
placeholder='Pilih Produk'
isDisabled
isDisabled={formState == 'edit'}
value={
selectedProduct
? ({
value: selectedProduct?.value,
label: salesOrders.find(
label: exisitingValues?.find(
(item) => item.id === selectedProduct?.value
)?.product_warehouse.product.name,
)?.marketing_product?.product_warehouse?.label,
} as OptionType)
: null
}
@@ -191,7 +192,7 @@ const DeliveryOrderProductForm = ({
const selected = value as OptionType;
setSelectedProduct(selected);
const so = salesOrders.find(
const so = salesOrders?.find(
(item) => item.id === selected?.value
);
if (!so) {
@@ -212,7 +213,7 @@ const DeliveryOrderProductForm = ({
formik.setValues({
...formik.values,
marketing_product_id: selected.value as number,
marketing_product: MarketingProductToFieldValues(so),
marketing_product: SalesProductToFieldValues(so),
qty: formik.values.qty || so.qty,
unit_price: so.unit_price,
total_price: so.total_price,
@@ -230,9 +231,9 @@ const DeliveryOrderProductForm = ({
className={{ badge: 'whitespace-nowrap font-semibold' }}
>
{
salesOrders.find(
exisitingValues?.find(
(item) => item.id === selectedProduct?.value
)?.product_warehouse?.warehouse?.name
)?.marketing_product?.kandang?.label
}
</Badge>
)
@@ -254,6 +255,9 @@ const DeliveryOrderProductForm = ({
}
errorMessage={formik.errors.delivery_date}
placeholder='Pilih Tanggal'
className={{
inputWrapper: 'bg-white',
}}
required
/>
@@ -278,7 +282,10 @@ const DeliveryOrderProductForm = ({
label='Kuantitas'
name='qty'
value={formik.values.qty}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('qty')}
isError={Boolean(formik.errors.qty)}
errorMessage={formik.errors.qty}
@@ -290,7 +297,10 @@ const DeliveryOrderProductForm = ({
label='Avg. Bobot (Kg)'
name='avg_weight'
value={formik.values.avg_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('avg_weight')}
isError={Boolean(formik.errors.avg_weight)}
errorMessage={formik.errors.avg_weight}
@@ -302,7 +312,10 @@ const DeliveryOrderProductForm = ({
label='Harga Satuan (Rp)'
name='unit_price'
value={formik.values.unit_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('unit_price')}
isError={Boolean(formik.errors.unit_price)}
errorMessage={formik.errors.unit_price}
@@ -314,7 +327,10 @@ const DeliveryOrderProductForm = ({
label='Total Bobot (Kg)'
name='total_weight'
value={formik.values.total_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_weight')}
isError={Boolean(formik.errors.total_weight)}
errorMessage={formik.errors.total_weight}
@@ -326,7 +342,10 @@ const DeliveryOrderProductForm = ({
label='Total Penjualan (Rp)'
name='total_price'
value={formik.values.total_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_price')}
isError={Boolean(formik.errors.total_price)}
errorMessage={formik.errors.total_price}
@@ -5,7 +5,7 @@ import {
SalesOrderProductFormValues,
SalesOrderProductSchema,
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
import { RefObject, useState } from 'react';
import { RefObject, useMemo, useState } from 'react';
import SelectInput, {
OptionType,
useSelect,
@@ -20,16 +20,20 @@ import { isResponseSuccess } from '@/lib/api-helper';
import { formatVechicleNumber } from '@/lib/helper';
import PatternInput from '@/components/input/PatternInput';
import Alert from '@/components/Alert';
import { ProductCalculationFields, recalculate } from '../../MarketingForm';
const SalesOrderProductForm = ({
initialValues,
exisitingValues,
onSubmitForm,
}: {
initialValues?: SalesOrderProductFormValues;
exisitingValues?: SalesOrderProductFormValues[];
modalRef?: RefObject<HTMLDialogElement | null>;
onSubmitForm?: (value: SalesOrderProductFormValues) => Promise<void>;
}) => {
const [formErrorMessage, setFormErrorMessage] = useState('');
const [currentInput, setCurrentInput] = useState<string>('');
const formik = useFormik<SalesOrderProductFormValues>({
enableReinitialize: true,
@@ -51,6 +55,8 @@ const SalesOrderProductForm = ({
onSubmitForm?.(values);
handleResetForm();
},
validateOnBlur: true,
isInitialValid: false,
});
const {
@@ -72,6 +78,15 @@ const SalesOrderProductForm = ({
}
);
const productOptionsFiltered = useMemo(() => {
return warehouseSourceOptions.filter(
(product) =>
!exisitingValues
?.map((item) => item.product_warehouse_id)
.includes(product.value)
);
}, [warehouseSourceOptions, exisitingValues]);
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldValue('kandang', val as OptionType);
formik.setFieldValue('kandang_id', (val as OptionType)?.value);
@@ -115,6 +130,7 @@ const SalesOrderProductForm = ({
};
const handleBlurField = (field: string) => {
setCurrentInput(field);
const { qty, unit_price, total_price, avg_weight, total_weight } =
formik.values;
@@ -151,7 +167,11 @@ const SalesOrderProductForm = ({
<>
<form
className='size-full'
onSubmit={formik.handleSubmit}
onSubmit={(e) => {
e.preventDefault();
handleBlurField(currentInput);
formik.handleSubmit(e);
}}
onReset={handleResetForm}
>
{formErrorMessage && (
@@ -200,12 +220,18 @@ const SalesOrderProductForm = ({
<SelectInput
required
label='Produk'
options={warehouseSourceOptions}
options={productOptionsFiltered}
isLoading={isLoadingWarehouseSourceOptions}
value={formik.values.product_warehouse}
onChange={warehouseChangeHandler}
isClearable
placeholder='Pilih Kandang Terlebih Dahulu'
placeholder={
formik.values.kandang_id
? productOptionsFiltered.length == 0
? 'Tidak ada produk yang tersedia'
: 'Pilih produk'
: 'Pilih Kandang Terlebih Dahulu'
}
isDisabled={!formik.values.kandang_id}
isError={
formik.touched.product_warehouse_id &&
@@ -218,7 +244,10 @@ const SalesOrderProductForm = ({
label='Kuantitas'
name='qty'
value={formik.values.qty}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('qty')}
isError={formik.touched.qty && Boolean(formik.errors.qty)}
errorMessage={formik.errors.qty}
@@ -229,7 +258,10 @@ const SalesOrderProductForm = ({
label='Avg. Bobot (Kg)'
name='avg_weight'
value={formik.values.avg_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('avg_weight')}
isError={
formik.touched.avg_weight && Boolean(formik.errors.avg_weight)
@@ -242,7 +274,10 @@ const SalesOrderProductForm = ({
label='Harga Satuan (Rp)'
name='unit_price'
value={formik.values.unit_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('unit_price')}
isError={
formik.touched.unit_price && Boolean(formik.errors.unit_price)
@@ -255,7 +290,10 @@ const SalesOrderProductForm = ({
label='Total Bobot (Kg)'
name='total_weight'
value={formik.values.total_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_weight')}
isError={
formik.touched.total_weight && Boolean(formik.errors.total_weight)
@@ -268,7 +306,10 @@ const SalesOrderProductForm = ({
label='Total Penjualan (Rp)'
name='total_price'
value={formik.values.total_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_price')}
isError={
formik.touched.total_price && Boolean(formik.errors.total_price)