refactor(FE-208,212,213): add transport-related fields and update form handling in PurchaseOrder forms

This commit is contained in:
rstubryan
2025-11-19 10:19:05 +07:00
parent 89d9d40713
commit b520b4ee54
5 changed files with 150 additions and 99 deletions
@@ -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';
@@ -39,18 +39,6 @@ const PurchaseOrderAcceptApprovalForm = ({
useState(''); useState('');
// ===== TYPE DEFINITIONS ===== // ===== TYPE DEFINITIONS =====
interface PurchaseItemOptionType extends OptionType {
id: number;
quantity: number;
product: {
name: string;
product_category: string | { name: string };
uom: {
name: string;
};
};
}
interface ExpeditionVendorOptionType { interface ExpeditionVendorOptionType {
value: number; value: number;
label: string; label: string;
@@ -145,32 +133,29 @@ const PurchaseOrderAcceptApprovalForm = ({
onSubmit: async (values) => { onSubmit: async (values) => {
const payload: CreateAcceptApprovalRequestPayload = { const payload: CreateAcceptApprovalRequestPayload = {
notes: values.notes || '', notes: values.notes || '',
items: (values.items || []).map((item) => ({ items:
purchase_item_id: values.items?.map((formItem) => {
typeof item.purchase_item_id === 'string' return {
? parseInt(item.purchase_item_id) || 0 purchase_item_id: formItem.purchase_item_id || 0,
: item.purchase_item_id || 0, received_date: formItem.received_date || '',
received_date: item.received_date || '', travel_number: formItem.travel_number || '',
travel_number: item.travel_number || '', travel_document_path: formItem.travel_document_path || '',
travel_document_path: item.travel_document_path || '', vehicle_number: formItem.vehicle_number || '',
vehicle_number: item.vehicle_number || '', expedition_vendor_id: formItem.expedition_vendor_id || 0,
expedition_vendor_id:
typeof item.expedition_vendor_id === 'string'
? parseInt(item.expedition_vendor_id) || 0
: item.expedition_vendor_id || 0,
received_qty: received_qty:
typeof item.received_qty === 'string' typeof formItem.received_qty === 'string'
? parseFloat(item.received_qty) || 0 ? parseFloat(formItem.received_qty) || 0
: item.received_qty || 0, : formItem.received_qty || 0,
transport_per_item: transport_per_item:
typeof item.transport_per_item === 'string' typeof formItem.transport_per_item === 'string'
? parseFloat(item.transport_per_item) || 0 ? parseFloat(formItem.transport_per_item) || 0
: item.transport_per_item || 0, : formItem.transport_per_item || 0,
transport_total: transport_total:
typeof item.transport_total === 'string' typeof formItem.transport_total === 'string'
? parseFloat(item.transport_total) || 0 ? parseFloat(formItem.transport_total) || 0
: item.transport_total || 0, : formItem.transport_total || 0,
})), };
}) || [],
}; };
switch (type) { switch (type) {
@@ -192,13 +177,13 @@ const PurchaseOrderAcceptApprovalForm = ({
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} ${item.product.uom?.name || 'unit'})`, label: item.product.name,
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 || '',
uom: item.product.uom, uom: item.product.uom || { name: 'unit' },
}, },
})); }));
} }
@@ -206,33 +191,64 @@ const PurchaseOrderAcceptApprovalForm = ({
return []; return [];
}, [initialValues?.items]); }, [initialValues?.items]);
const expeditionVendors: ExpeditionVendorOptionType[] = useMemo(() => { useEffect(() => {
return []; if (purchaseItems.length > 0 && initialValues?.items) {
}, []); const updatedItems = initialValues.items.map((item) => {
return {
purchase_item: null,
purchase_item_id: item.id,
received_date: item.received_date
? new Date(item.received_date).toISOString().split('T')[0]
: '',
travel_number: item.travel_number || '',
travel_document_path: item.travel_document_path || '',
vehicle_number: item.vehicle_number || '',
expedition_vendor: null,
expedition_vendor_id: 0,
received_qty: item.sub_qty ? item.sub_qty.toString() : '',
transport_per_item: '',
transport_total: '',
};
});
formik.setFieldValue('items', updatedItems);
}
}, [purchaseItems, initialValues]);
const getPurchaseItemOptions = useCallback(() => { const expeditionVendors: ExpeditionVendorOptionType[] = useMemo(() => {
return purchaseItems; return [
}, [purchaseItems]); {
value: 1,
label: 'PT. Ekspedisi Sejahtera',
id: 1,
},
{
value: 2,
label: 'PT. Jaya Trans Logistics',
id: 2,
},
{
value: 3,
label: 'PT. Cargo Express Indonesia',
id: 3,
},
{
value: 4,
label: 'PT. Fast Delivery Service',
id: 4,
},
{
value: 5,
label: 'PT. Mitra Angkutan Sejahtera',
id: 5,
},
];
}, []);
const getExpeditionVendorOptions = useCallback(() => { const getExpeditionVendorOptions = useCallback(() => {
return expeditionVendors; return expeditionVendors;
}, [expeditionVendors]); }, [expeditionVendors]);
// ===== FIELD CHANGE HANDLERS ===== // ===== 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
);
};
const expeditionVendorChangeHandler = ( const expeditionVendorChangeHandler = (
idx: number, idx: number,
val: OptionType | OptionType[] | null val: OptionType | OptionType[] | null
@@ -298,14 +314,21 @@ const PurchaseOrderAcceptApprovalForm = ({
? 'Konfirmasi Penerimaan Produk' ? 'Konfirmasi Penerimaan Produk'
: 'Edit Penerimaan Produk'} : 'Edit Penerimaan Produk'}
</h2> </h2>
{/* Debug Info */}
<div className='mb-4 p-4 bg-gray-100 rounded-lg text-xs'>
<div>Form Valid: {formik.isValid ? '✅' : '❌'}</div>
<div>Form Submitting: {formik.isSubmitting ? '✅' : '❌'}</div>
<div>Form Errors: {JSON.stringify(formik.errors, null, 2)}</div>
<div>Form Touched: {JSON.stringify(formik.touched, null, 2)}</div>
</div>
<div className='overflow-x-auto'> <div className='overflow-x-auto'>
<table className='table'> <table className='table'>
<thead> <thead>
<tr> <tr>
<th> <th>Produk</th>
Item <th>Jumlah</th>
<span className='text-error'>*</span> <th>Satuan</th>
</th>
<th> <th>
Tanggal Diterima Tanggal Diterima
<span className='text-error'>*</span> <span className='text-error'>*</span>
@@ -341,29 +364,53 @@ const PurchaseOrderAcceptApprovalForm = ({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{formik.values.items?.map((item, idx) => { {purchaseItems?.map((purchaseItem, idx) => {
purchaseItems.find((p) => p.value === item.purchase_item_id); const formItem = formik.values.items?.[idx];
return ( return (
<tr key={`purchase-item-${idx}`}> <tr key={`purchase-item-${idx}`}>
<td> <td>
<SelectInput <TextInput
required name={`items.${idx}.product_name`}
isClearable={true} type='text'
value={item.purchase_item} value={purchaseItem?.product?.name || ''}
key={`purchase-item-${idx}`} readOnly={true}
onChange={(val) => purchaseItemChangeHandler(idx, val)}
options={getPurchaseItemOptions()}
isError={
isRepeaterInputError(idx, 'purchase_item_id').isError
}
errorMessage={
isRepeaterInputError(idx, 'purchase_item_id')
.errorMessage
}
placeholder='Pilih Item...'
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',
}} }}
disabled={true}
/>
<input
type='hidden'
name={`items.${idx}.purchase_item_id`}
value={purchaseItem?.value || 0}
/>
</td>
<td>
<TextInput
name={`items.${idx}.quantity`}
type='text'
value={
purchaseItem?.quantity
? purchaseItem.quantity.toLocaleString('id-ID')
: ''
}
readOnly={true}
className={{
wrapper: 'min-w-32',
}}
disabled={true}
/>
</td>
<td>
<TextInput
name={`items.${idx}.uom`}
type='text'
value={purchaseItem?.product?.uom?.name || ''}
readOnly={true}
className={{
wrapper: 'min-w-24',
}}
disabled={true}
/> />
</td> </td>
<td> <td>
@@ -371,7 +418,7 @@ const PurchaseOrderAcceptApprovalForm = ({
required required
isNestedModal={true} isNestedModal={true}
name={`items.${idx}.received_date`} name={`items.${idx}.received_date`}
value={item.received_date || ''} value={formItem?.received_date || ''}
onChange={(e) => onChange={(e) =>
formik.setFieldValue( formik.setFieldValue(
`items.${idx}.received_date`, `items.${idx}.received_date`,
@@ -396,7 +443,7 @@ const PurchaseOrderAcceptApprovalForm = ({
required required
name={`items.${idx}.travel_number`} name={`items.${idx}.travel_number`}
type='text' type='text'
value={item.travel_number || ''} value={formItem?.travel_number || ''}
onChange={(e) => onChange={(e) =>
formik.setFieldValue( formik.setFieldValue(
`items.${idx}.travel_number`, `items.${idx}.travel_number`,
@@ -422,7 +469,7 @@ const PurchaseOrderAcceptApprovalForm = ({
required required
name={`items.${idx}.travel_document_path`} name={`items.${idx}.travel_document_path`}
type='text' type='text'
value={item.travel_document_path || ''} value={formItem?.travel_document_path || ''}
onChange={(e) => onChange={(e) =>
formik.setFieldValue( formik.setFieldValue(
`items.${idx}.travel_document_path`, `items.${idx}.travel_document_path`,
@@ -449,7 +496,7 @@ const PurchaseOrderAcceptApprovalForm = ({
required required
name={`items.${idx}.vehicle_number`} name={`items.${idx}.vehicle_number`}
type='text' type='text'
value={item.vehicle_number || ''} value={formItem?.vehicle_number || ''}
onChange={(e) => onChange={(e) =>
formik.setFieldValue( formik.setFieldValue(
`items.${idx}.vehicle_number`, `items.${idx}.vehicle_number`,
@@ -474,7 +521,7 @@ const PurchaseOrderAcceptApprovalForm = ({
<SelectInput <SelectInput
required required
isClearable={true} isClearable={true}
value={item.expedition_vendor} value={formItem?.expedition_vendor}
key={`expedition-vendor-${idx}`} key={`expedition-vendor-${idx}`}
onChange={(val) => onChange={(val) =>
expeditionVendorChangeHandler(idx, val) expeditionVendorChangeHandler(idx, val)
@@ -498,7 +545,7 @@ const PurchaseOrderAcceptApprovalForm = ({
<NumberInput <NumberInput
required required
name={`items.${idx}.received_qty`} name={`items.${idx}.received_qty`}
value={item.received_qty || ''} value={formItem?.received_qty || ''}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(
idx, idx,
@@ -527,7 +574,7 @@ const PurchaseOrderAcceptApprovalForm = ({
<NumberInput <NumberInput
required required
name={`items.${idx}.transport_per_item`} name={`items.${idx}.transport_per_item`}
value={item.transport_per_item || ''} value={formItem?.transport_per_item || ''}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(
idx, idx,
@@ -559,7 +606,7 @@ const PurchaseOrderAcceptApprovalForm = ({
<NumberInput <NumberInput
required required
name={`items.${idx}.transport_total`} name={`items.${idx}.transport_total`}
value={item.transport_total || ''} value={formItem?.transport_total || ''}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(
idx, idx,
@@ -131,19 +131,18 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
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!')
.typeError('Purchase item is required!')
.test( .test(
'is-valid-purchase-item', 'is-valid-purchase-item',
'Purchase item must be selected!', 'Purchase item must be selected!',
function (value) { function (value) {
if (!this.parent.purchase_item) return true;
return Boolean(value && value > 0); return Boolean(value && value > 0);
} }
) ),
.typeError('Purchase item must be selected!'),
received_date: Yup.string() received_date: Yup.string()
.required('Tanggal penerimaan wajib diisi!') .required('Tanggal penerimaan wajib diisi!')
.typeError('Tanggal penerimaan wajib diisi!'), .typeError('Tanggal penerimaan wajib diisi!'),
@@ -405,12 +405,12 @@ const PurchaseOrderDetail = ({
cell: (props) => formatNumber(props.getValue() as number), cell: (props) => formatNumber(props.getValue() as number),
}, },
{ {
accessorKey: 'price', accessorKey: 'transport_per_item',
header: 'Transport /Item', header: 'Transport /Item',
cell: (props) => formatCurrency(props.getValue() as number), cell: (props) => formatCurrency(props.getValue() as number),
}, },
{ {
accessorKey: 'total_price', accessorKey: 'transport_total',
header: 'Transport Total', header: 'Transport Total',
cell: (props) => formatCurrency(props.getValue() as number), cell: (props) => formatCurrency(props.getValue() as number),
}, },
+1 -1
View File
@@ -90,7 +90,7 @@ export class AcceptApprovalService extends BaseApiService<
payload: CreateAcceptApprovalRequestPayload payload: CreateAcceptApprovalRequestPayload
): Promise<BaseApiResponse<{ message: string }> | undefined> { ): Promise<BaseApiResponse<{ message: string }> | undefined> {
return await this.customRequest<BaseApiResponse<{ message: string }>>( return await this.customRequest<BaseApiResponse<{ message: string }>>(
`${purchaseRequestId}/approvals/receipts`, `${purchaseRequestId}/receipts`,
{ {
method: 'POST', method: 'POST',
payload, payload,
+5
View File
@@ -38,6 +38,11 @@ export type PurchaseItem = {
travel_number_docs?: string | null; travel_number_docs?: string | null;
travel_document_path?: string | null; travel_document_path?: string | null;
vehicle_number?: string | null; vehicle_number?: string | null;
expedition_vendor_id?: number | null;
expedition_vendor_name?: string | null;
received_qty?: number | null;
transport_per_item?: number | null;
transport_total?: number | null;
}; };
export type BasePurchase = { export type BasePurchase = {