mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
refactor(FE-208,212,213): add transport-related fields and update form handling in PurchaseOrder forms
This commit is contained in:
@@ -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';
|
||||
@@ -39,18 +39,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
useState('');
|
||||
|
||||
// ===== TYPE DEFINITIONS =====
|
||||
interface PurchaseItemOptionType extends OptionType {
|
||||
id: number;
|
||||
quantity: number;
|
||||
product: {
|
||||
name: string;
|
||||
product_category: string | { name: string };
|
||||
uom: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface ExpeditionVendorOptionType {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -145,32 +133,29 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
onSubmit: async (values) => {
|
||||
const payload: CreateAcceptApprovalRequestPayload = {
|
||||
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,
|
||||
received_date: item.received_date || '',
|
||||
travel_number: item.travel_number || '',
|
||||
travel_document_path: item.travel_document_path || '',
|
||||
vehicle_number: item.vehicle_number || '',
|
||||
expedition_vendor_id:
|
||||
typeof item.expedition_vendor_id === 'string'
|
||||
? parseInt(item.expedition_vendor_id) || 0
|
||||
: item.expedition_vendor_id || 0,
|
||||
received_qty:
|
||||
typeof item.received_qty === 'string'
|
||||
? parseFloat(item.received_qty) || 0
|
||||
: item.received_qty || 0,
|
||||
transport_per_item:
|
||||
typeof item.transport_per_item === 'string'
|
||||
? parseFloat(item.transport_per_item) || 0
|
||||
: item.transport_per_item || 0,
|
||||
transport_total:
|
||||
typeof item.transport_total === 'string'
|
||||
? parseFloat(item.transport_total) || 0
|
||||
: item.transport_total || 0,
|
||||
})),
|
||||
items:
|
||||
values.items?.map((formItem) => {
|
||||
return {
|
||||
purchase_item_id: formItem.purchase_item_id || 0,
|
||||
received_date: formItem.received_date || '',
|
||||
travel_number: formItem.travel_number || '',
|
||||
travel_document_path: formItem.travel_document_path || '',
|
||||
vehicle_number: formItem.vehicle_number || '',
|
||||
expedition_vendor_id: formItem.expedition_vendor_id || 0,
|
||||
received_qty:
|
||||
typeof formItem.received_qty === 'string'
|
||||
? parseFloat(formItem.received_qty) || 0
|
||||
: formItem.received_qty || 0,
|
||||
transport_per_item:
|
||||
typeof formItem.transport_per_item === 'string'
|
||||
? parseFloat(formItem.transport_per_item) || 0
|
||||
: formItem.transport_per_item || 0,
|
||||
transport_total:
|
||||
typeof formItem.transport_total === 'string'
|
||||
? parseFloat(formItem.transport_total) || 0
|
||||
: formItem.transport_total || 0,
|
||||
};
|
||||
}) || [],
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
@@ -192,13 +177,13 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
if (initialValues?.items) {
|
||||
return initialValues.items.map((item) => ({
|
||||
value: item.id,
|
||||
label: `${item.product.name} (${item.quantity} ${item.product.uom?.name || 'unit'})`,
|
||||
label: item.product.name,
|
||||
id: item.id,
|
||||
quantity: item.quantity,
|
||||
quantity: item.sub_qty,
|
||||
product: {
|
||||
name: item.product.name,
|
||||
product_category: item.product.product_category,
|
||||
uom: item.product.uom,
|
||||
product_category: item.product.product_category || '',
|
||||
uom: item.product.uom || { name: 'unit' },
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -206,33 +191,64 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
return [];
|
||||
}, [initialValues?.items]);
|
||||
|
||||
const expeditionVendors: ExpeditionVendorOptionType[] = useMemo(() => {
|
||||
return [];
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
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(() => {
|
||||
return purchaseItems;
|
||||
}, [purchaseItems]);
|
||||
const expeditionVendors: ExpeditionVendorOptionType[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
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(() => {
|
||||
return expeditionVendors;
|
||||
}, [expeditionVendors]);
|
||||
|
||||
// ===== 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 = (
|
||||
idx: number,
|
||||
val: OptionType | OptionType[] | null
|
||||
@@ -298,14 +314,21 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
? 'Konfirmasi Penerimaan Produk'
|
||||
: 'Edit Penerimaan Produk'}
|
||||
</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'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Item
|
||||
<span className='text-error'>*</span>
|
||||
</th>
|
||||
<th>Produk</th>
|
||||
<th>Jumlah</th>
|
||||
<th>Satuan</th>
|
||||
<th>
|
||||
Tanggal Diterima
|
||||
<span className='text-error'>*</span>
|
||||
@@ -341,29 +364,53 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.items?.map((item, idx) => {
|
||||
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={
|
||||
isRepeaterInputError(idx, 'purchase_item_id').isError
|
||||
}
|
||||
errorMessage={
|
||||
isRepeaterInputError(idx, 'purchase_item_id')
|
||||
.errorMessage
|
||||
}
|
||||
placeholder='Pilih Item...'
|
||||
<TextInput
|
||||
name={`items.${idx}.product_name`}
|
||||
type='text'
|
||||
value={purchaseItem?.product?.name || ''}
|
||||
readOnly={true}
|
||||
className={{
|
||||
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>
|
||||
@@ -371,7 +418,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
required
|
||||
isNestedModal={true}
|
||||
name={`items.${idx}.received_date`}
|
||||
value={item.received_date || ''}
|
||||
value={formItem?.received_date || ''}
|
||||
onChange={(e) =>
|
||||
formik.setFieldValue(
|
||||
`items.${idx}.received_date`,
|
||||
@@ -396,7 +443,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
required
|
||||
name={`items.${idx}.travel_number`}
|
||||
type='text'
|
||||
value={item.travel_number || ''}
|
||||
value={formItem?.travel_number || ''}
|
||||
onChange={(e) =>
|
||||
formik.setFieldValue(
|
||||
`items.${idx}.travel_number`,
|
||||
@@ -422,7 +469,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
required
|
||||
name={`items.${idx}.travel_document_path`}
|
||||
type='text'
|
||||
value={item.travel_document_path || ''}
|
||||
value={formItem?.travel_document_path || ''}
|
||||
onChange={(e) =>
|
||||
formik.setFieldValue(
|
||||
`items.${idx}.travel_document_path`,
|
||||
@@ -449,7 +496,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
required
|
||||
name={`items.${idx}.vehicle_number`}
|
||||
type='text'
|
||||
value={item.vehicle_number || ''}
|
||||
value={formItem?.vehicle_number || ''}
|
||||
onChange={(e) =>
|
||||
formik.setFieldValue(
|
||||
`items.${idx}.vehicle_number`,
|
||||
@@ -474,7 +521,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
<SelectInput
|
||||
required
|
||||
isClearable={true}
|
||||
value={item.expedition_vendor}
|
||||
value={formItem?.expedition_vendor}
|
||||
key={`expedition-vendor-${idx}`}
|
||||
onChange={(val) =>
|
||||
expeditionVendorChangeHandler(idx, val)
|
||||
@@ -498,7 +545,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
<NumberInput
|
||||
required
|
||||
name={`items.${idx}.received_qty`}
|
||||
value={item.received_qty || ''}
|
||||
value={formItem?.received_qty || ''}
|
||||
onChange={(e) =>
|
||||
handlePurchaseItemChange(
|
||||
idx,
|
||||
@@ -527,7 +574,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
<NumberInput
|
||||
required
|
||||
name={`items.${idx}.transport_per_item`}
|
||||
value={item.transport_per_item || ''}
|
||||
value={formItem?.transport_per_item || ''}
|
||||
onChange={(e) =>
|
||||
handlePurchaseItemChange(
|
||||
idx,
|
||||
@@ -559,7 +606,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
||||
<NumberInput
|
||||
required
|
||||
name={`items.${idx}.transport_total`}
|
||||
value={item.transport_total || ''}
|
||||
value={formItem?.transport_total || ''}
|
||||
onChange={(e) =>
|
||||
handlePurchaseItemChange(
|
||||
idx,
|
||||
|
||||
@@ -131,19 +131,18 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
|
||||
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!')
|
||||
.typeError('Purchase item is required!')
|
||||
.test(
|
||||
'is-valid-purchase-item',
|
||||
'Purchase item must be selected!',
|
||||
function (value) {
|
||||
if (!this.parent.purchase_item) return true;
|
||||
return Boolean(value && value > 0);
|
||||
}
|
||||
)
|
||||
.typeError('Purchase item must be selected!'),
|
||||
),
|
||||
received_date: Yup.string()
|
||||
.required('Tanggal penerimaan wajib diisi!')
|
||||
.typeError('Tanggal penerimaan wajib diisi!'),
|
||||
|
||||
@@ -405,12 +405,12 @@ const PurchaseOrderDetail = ({
|
||||
cell: (props) => formatNumber(props.getValue() as number),
|
||||
},
|
||||
{
|
||||
accessorKey: 'price',
|
||||
accessorKey: 'transport_per_item',
|
||||
header: 'Transport /Item',
|
||||
cell: (props) => formatCurrency(props.getValue() as number),
|
||||
},
|
||||
{
|
||||
accessorKey: 'total_price',
|
||||
accessorKey: 'transport_total',
|
||||
header: 'Transport Total',
|
||||
cell: (props) => formatCurrency(props.getValue() as number),
|
||||
},
|
||||
|
||||
@@ -90,7 +90,7 @@ export class AcceptApprovalService extends BaseApiService<
|
||||
payload: CreateAcceptApprovalRequestPayload
|
||||
): Promise<BaseApiResponse<{ message: string }> | undefined> {
|
||||
return await this.customRequest<BaseApiResponse<{ message: string }>>(
|
||||
`${purchaseRequestId}/approvals/receipts`,
|
||||
`${purchaseRequestId}/receipts`,
|
||||
{
|
||||
method: 'POST',
|
||||
payload,
|
||||
|
||||
Vendored
+5
@@ -38,6 +38,11 @@ export type PurchaseItem = {
|
||||
travel_number_docs?: string | null;
|
||||
travel_document_path?: 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 = {
|
||||
|
||||
Reference in New Issue
Block a user