feat(FE): Add travel document upload to purchase form

This commit is contained in:
rstubryan
2025-12-31 11:02:06 +07:00
parent e32b9ddcb2
commit 5af00faa32
4 changed files with 75 additions and 6 deletions
@@ -8,6 +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 FileInput from '@/components/input/FileInput';
import SelectInput, {
OptionType,
useSelect,
@@ -66,6 +67,7 @@ const PurchaseOrderAcceptApprovalForm = ({
| 'expedition_vendor_id'
| 'received_qty'
| 'transport_per_item'
| 'travel_documents'
): { isError: boolean; errorMessage: string } => {
const touchedItem = formik.touched.items?.[idx];
const errorItem = formik.errors.items?.[idx] as
@@ -185,6 +187,7 @@ const PurchaseOrderAcceptApprovalForm = ({
: formItem.transport_per_item || 0,
};
}) || [],
travel_documents: values.travel_documents || [],
};
switch (type) {
@@ -236,15 +239,29 @@ const PurchaseOrderAcceptApprovalForm = ({
travel_document_path: item.travel_document_path || '',
vehicle_number: item.vehicle_number || '',
expedition_vendor: null,
expedition_vendor_id: 0,
expedition_vendor_id: item.expedition_vendor_id || 0,
received_qty: item.total_qty || '',
transport_per_item: '',
transport_per_item: item.transport_per_item || '',
};
});
formik.setFieldValue('items', updatedItems);
}
}, [purchaseItems, initialValues]);
useEffect(() => {
if (
formik.values.travel_documents &&
formik.values.travel_documents.length > 0
) {
const fileNames = formik.values.travel_documents
.map((file) => file.name)
.join(', ');
formik.values.items?.forEach((item, idx) => {
formik.setFieldValue(`items.${idx}.travel_document_path`, fileNames);
});
}
}, [formik.values.travel_documents]);
// ===== HELPER FUNCTIONS =====
const getQuantityExceededError = useCallback(
(idx: number, receivedQty: number) => {
@@ -343,7 +360,7 @@ const PurchaseOrderAcceptApprovalForm = ({
No. Surat Jalan
<span className='text-error'>*</span>
</th>
<th>
<th className='hidden'>
Dokumen Surat Jalan
<span className='text-error'>*</span>
</th>
@@ -478,7 +495,7 @@ const PurchaseOrderAcceptApprovalForm = ({
}}
/>
</td>
<td>
<td className='hidden'>
<TextInput
required
name={`items.${idx}.travel_document_path`}
@@ -636,7 +653,7 @@ const PurchaseOrderAcceptApprovalForm = ({
</tbody>
</table>
</div>
<div className={'col-span-2'}>
<div className={'col-span-2 my-2'}>
<TextInput
label='Notes'
name='notes'
@@ -649,6 +666,31 @@ const PurchaseOrderAcceptApprovalForm = ({
/>
</div>
<div className={'col-span-2 my-2'}>
<FileInput
required
name='travel_documents'
label='Dokumen Surat Jalan'
accept='.pdf,.jpg,.jpeg,.png'
onChange={(e) => {
const files = Array.from(e.target.files || []);
formik.setFieldValue('travel_documents', files);
}}
onBlur={formik.handleBlur}
bottomLabel={
formik.values.travel_documents &&
formik.values.travel_documents.length > 0
? `${formik.values.travel_documents.length} file(s) dipilih`
: undefined
}
isError={
formik.touched.travel_documents &&
Boolean(formik.errors.travel_documents)
}
errorMessage={formik.errors.travel_documents as string}
/>
</div>
{/* Action buttons */}
<div className='flex flex-row justify-between gap-2 flex-wrap mt-5'>
<div className='flex flex-row justify-end gap-2 w-full'>
@@ -48,6 +48,7 @@ type PurchaseRequestAcceptApprovalFormSchemaType = {
received_qty: number | string;
transport_per_item: number | string;
}[];
travel_documents: File[];
};
export type PurchaseStaffApprovalItemSchema = {
@@ -379,6 +380,11 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema<PurchaseR
.min(1, 'Minimal harus ada 1 item pembelian!')
.required('Item pembelian wajib diisi!')
.typeError('Item pembelian wajib diisi!'),
travel_documents: Yup.array()
.of(Yup.mixed<File>().required())
.required('Dokumen surat jalan wajib diupload!')
.min(1, 'Minimal upload 1 dokumen surat jalan!')
.typeError('Dokumen surat jalan wajib diupload!'),
});
export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType =
@@ -397,6 +403,7 @@ export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcce
transport_per_item: '',
},
],
travel_documents: [],
};
export const PurchaseRequestAcceptApprovalFormDefaultValues = (
@@ -428,6 +435,7 @@ export const PurchaseRequestAcceptApprovalFormDefaultValues = (
transport_per_item: '',
},
],
travel_documents: [],
};
};
+19 -1
View File
@@ -72,11 +72,29 @@ export const PurchaseApi = {
purchaseRequestId: number,
payload: CreateAcceptApprovalRequestPayload
): Promise<BaseApiResponse<{ message: string }> | undefined> => {
const formData = new FormData();
formData.append('action', payload.action);
if (payload.notes) {
formData.append('notes', payload.notes);
}
if (payload.items) {
formData.append('items', JSON.stringify(payload.items));
}
if (payload.travel_documents) {
payload.travel_documents.forEach((file) => {
formData.append('travel_documents', file);
});
}
return await basePurchaseApi.customRequest<
BaseApiResponse<{ message: string }>
>(`${purchaseRequestId}/receipts`, {
method: 'POST',
payload,
payload: formData as unknown as Record<string, unknown>,
});
},
},
+1
View File
@@ -118,6 +118,7 @@ export type CreateAcceptApprovalRequestPayload = {
received_qty: number;
transport_per_item: number;
}[];
travel_documents?: File[];
};
export type DeletePurchaseRequestItemPayload = {