mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE-212): update PurchaseRequestForm schema and validation, streamline warehouse handling and add sub quantity field
This commit is contained in:
@@ -26,9 +26,9 @@ export const PurchaseRequestFormSchema = Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
warehouse_ids: Yup.number()
|
||||
warehouse_id: Yup.number()
|
||||
.required('Warehouse wajib diisi!')
|
||||
.min(1, 'Produk wajib diisi!')
|
||||
.min(1, 'Warehouse wajib diisi!')
|
||||
.typeError('Warehouse harus berupa angka!'),
|
||||
product: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
@@ -42,17 +42,14 @@ export const PurchaseRequestFormSchema = Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
product_warehouse_id: Yup.number()
|
||||
.required('Product warehouse wajib diisi!')
|
||||
.min(1, 'Product warehouse wajib diisi!')
|
||||
.typeError('Product warehouse harus berupa angka!'),
|
||||
total_qty: Yup.number()
|
||||
.required('Jumlah total wajib diisi!')
|
||||
.min(1, 'Jumlah total tidak boleh negatif!')
|
||||
.typeError('Jumlah total harus berupa angka!'),
|
||||
product_warehouse_id: Yup.number().optional().nullable(),
|
||||
sub_qty: Yup.number()
|
||||
.required('Sub Qty wajib diisi!')
|
||||
.min(0.001, 'Sub Qty tidak boleh negatif!')
|
||||
.typeError('Sub Qty harus berupa angka!'),
|
||||
price: Yup.number()
|
||||
.required('Harga wajib diisi!')
|
||||
.min(1, 'Harga tidak boleh negatif!')
|
||||
.min(0, 'Harga tidak boleh negatif!')
|
||||
.typeError('Harga harus berupa angka!'),
|
||||
})
|
||||
)
|
||||
@@ -99,7 +96,7 @@ export const getPurchaseRequestFormInitialValues = (
|
||||
label: item.warehouse.name,
|
||||
}
|
||||
: null,
|
||||
warehouse_ids: item.warehouse_ids,
|
||||
warehouse_id: item.warehouse_id,
|
||||
product: item.product
|
||||
? {
|
||||
value: item.product.id,
|
||||
@@ -113,19 +110,19 @@ export const getPurchaseRequestFormInitialValues = (
|
||||
label: item.product_warehouse.product.name,
|
||||
}
|
||||
: null,
|
||||
product_warehouse_id: item.product_warehouse_id,
|
||||
total_qty: item.total_qty,
|
||||
product_warehouse_id: item.product_warehouse_id || null,
|
||||
sub_qty: item.sub_qty,
|
||||
price: item.price,
|
||||
})
|
||||
) ?? [
|
||||
{
|
||||
warehouse: null,
|
||||
warehouse_ids: 0,
|
||||
warehouse_id: 0,
|
||||
product: null,
|
||||
product_id: 0,
|
||||
product_warehouse: null,
|
||||
product_warehouse_id: 0,
|
||||
total_qty: 0,
|
||||
product_warehouse_id: null,
|
||||
sub_qty: 0,
|
||||
price: 0,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import useSWR from 'swr';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Button from '@/components/Button';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||
import { FormActions } from '@/components/helper/form/FormActions';
|
||||
|
||||
@@ -18,8 +21,8 @@ import {
|
||||
} from './PurchaseRequestForm.schema';
|
||||
import { SupplierApi } from '@/services/api/master-data';
|
||||
import { WarehouseApi } from '@/services/api/master-data';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { usePurchaseRequestFormHandlers } from './usePurchaseRequestFormHandlers';
|
||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||
import { PurchaseApi } from '@/services/api/purchasing';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import {
|
||||
@@ -36,19 +39,61 @@ const PurchaseRequestForm = ({
|
||||
type = 'add',
|
||||
initialValues,
|
||||
}: PurchaseRequestFormProps) => {
|
||||
const router = useRouter();
|
||||
const deleteModal = useModal();
|
||||
|
||||
const [selectedPurchaseItems, setSelectedPurchaseItems] = useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [purchaseRequestFormErrorMessage, setPurchaseRequestFormErrorMessage] =
|
||||
useState('');
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const {
|
||||
deleteModal,
|
||||
purchaseRequestFormErrorMessage,
|
||||
isDeleteLoading,
|
||||
createPurchaseRequestHandler,
|
||||
updatePurchaseRequestHandler,
|
||||
deletePurchaseRequestClickHandler,
|
||||
confirmationModalDeleteClickHandler,
|
||||
} = usePurchaseRequestFormHandlers(initialValues?.id);
|
||||
// ===== FORM HANDLERS =====
|
||||
const createPurchaseRequestHandler = useCallback(
|
||||
async (payload: CreatePurchaseRequestPayload) => {
|
||||
const res = await PurchaseApi.create(payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseRequestFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
router.push('/purchase');
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const updatePurchaseRequestHandler = useCallback(
|
||||
async (
|
||||
purchaseRequestId: number,
|
||||
payload: CreatePurchaseRequestPayload
|
||||
) => {
|
||||
const res = await PurchaseApi.update(purchaseRequestId, payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseRequestFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
router.refresh();
|
||||
router.push('/purchase');
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const deletePurchaseRequestClickHandler = useCallback(() => {
|
||||
deleteModal.openModal();
|
||||
}, [deleteModal]);
|
||||
|
||||
const confirmationModalDeleteClickHandler = useCallback(async () => {
|
||||
if (!initialValues?.id) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
await PurchaseApi.delete(initialValues.id);
|
||||
deleteModal.closeModal();
|
||||
toast.success('Successfully delete Purchase Request!');
|
||||
setIsDeleteLoading(false);
|
||||
router.push('/purchase');
|
||||
}, [deleteModal, initialValues?.id, router]);
|
||||
|
||||
// ===== API DATA FETCHING =====
|
||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
||||
@@ -101,9 +146,10 @@ const PurchaseRequestForm = ({
|
||||
credit_term: values.credit_term || 0,
|
||||
notes: values.notes || '',
|
||||
purchase_items: (values.purchase_items || []).map((item) => ({
|
||||
warehouse_ids: item.warehouse_ids || 0,
|
||||
warehouse_id: item.warehouse_id || 0,
|
||||
product_id: item.product_id || 0,
|
||||
product_warehouse_id: item.product_warehouse_id || 0,
|
||||
product_warehouse_id: item.product_warehouse_id || undefined,
|
||||
sub_qty: item.sub_qty || 0,
|
||||
total_qty: item.total_qty || 0,
|
||||
price:
|
||||
typeof item.price === 'number'
|
||||
@@ -147,11 +193,12 @@ const PurchaseRequestForm = ({
|
||||
...(formik.values.purchase_items || []),
|
||||
{
|
||||
warehouse: null,
|
||||
warehouse_ids: 0,
|
||||
warehouse_id: 0,
|
||||
product: null,
|
||||
product_id: 0,
|
||||
product_warehouse: null,
|
||||
product_warehouse_id: 0,
|
||||
product_warehouse_id: null,
|
||||
sub_qty: 0,
|
||||
total_qty: 0,
|
||||
price: 0,
|
||||
},
|
||||
@@ -180,12 +227,12 @@ const PurchaseRequestForm = ({
|
||||
value: string | number
|
||||
) => {
|
||||
const integerFields = [
|
||||
'warehouse_ids',
|
||||
'warehouse_id',
|
||||
'product_id',
|
||||
'product_warehouse_id',
|
||||
'total_qty',
|
||||
];
|
||||
const floatFields = ['price'];
|
||||
const floatFields = ['price', 'sub_qty'];
|
||||
|
||||
if (integerFields.includes(field)) {
|
||||
const numValue = typeof value === 'string' ? parseInt(value) || 0 : value;
|
||||
@@ -320,6 +367,10 @@ const PurchaseRequestForm = ({
|
||||
Product Warehouse ID
|
||||
<span className='text-error'>*</span>
|
||||
</th>
|
||||
<th>
|
||||
Sub Qty
|
||||
<span className='text-error'>*</span>
|
||||
</th>
|
||||
<th>
|
||||
Total Qty
|
||||
<span className='text-error'>*</span>
|
||||
@@ -358,18 +409,18 @@ const PurchaseRequestForm = ({
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`purchase_items.${idx}.warehouse_ids`}
|
||||
value={item.warehouse_ids}
|
||||
name={`purchase_items.${idx}.warehouse_id`}
|
||||
value={item.warehouse_id}
|
||||
onChange={(e) =>
|
||||
handlePurchaseItemChange(
|
||||
idx,
|
||||
'warehouse_ids',
|
||||
'warehouse_id',
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
onBlur={formik.handleBlur}
|
||||
type='number'
|
||||
placeholder='Warehouse IDs'
|
||||
placeholder='Warehouse ID'
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'min-w-24',
|
||||
@@ -399,7 +450,6 @@ const PurchaseRequestForm = ({
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`purchase_items.${idx}.product_warehouse_id`}
|
||||
value={item.product_warehouse_id || ''}
|
||||
onChange={(e) =>
|
||||
@@ -418,6 +468,27 @@ const PurchaseRequestForm = ({
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`purchase_items.${idx}.sub_qty`}
|
||||
value={item.sub_qty}
|
||||
onChange={(e) =>
|
||||
handlePurchaseItemChange(
|
||||
idx,
|
||||
'sub_qty',
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
onBlur={formik.handleBlur}
|
||||
type='number'
|
||||
placeholder='Sub Qty'
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'min-w-24',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import { PurchaseApi } from '@/services/api/purchasing';
|
||||
import {
|
||||
CreatePurchaseRequestPayload,
|
||||
UpdatePurchaseRequestPayload,
|
||||
} from '@/types/api/purchase/purchase';
|
||||
import { isResponseError } from '@/lib/api-helper';
|
||||
|
||||
export const usePurchaseRequestFormHandlers = (initialValuesId?: number) => {
|
||||
const router = useRouter();
|
||||
const deleteModal = useModal();
|
||||
const [purchaseRequestFormErrorMessage, setPurchaseRequestFormErrorMessage] =
|
||||
useState('');
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const createPurchaseRequestHandler = useCallback(
|
||||
async (payload: CreatePurchaseRequestPayload) => {
|
||||
const res = await PurchaseApi.create(payload);
|
||||
if (isResponseError(res)) {
|
||||
setPurchaseRequestFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
router.push('/purchase');
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const updatePurchaseRequestHandler = useCallback(
|
||||
async (
|
||||
purchaseRequestId: number,
|
||||
payload: UpdatePurchaseRequestPayload
|
||||
) => {
|
||||
const res = await PurchaseApi.update(purchaseRequestId, payload);
|
||||
if (res?.status === 'error') {
|
||||
setPurchaseRequestFormErrorMessage(res.message);
|
||||
return;
|
||||
}
|
||||
toast.success(res?.message as string);
|
||||
router.refresh();
|
||||
router.push('/purchase');
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const deletePurchaseRequestClickHandler = useCallback(() => {
|
||||
deleteModal.openModal();
|
||||
}, [deleteModal]);
|
||||
|
||||
const confirmationModalDeleteClickHandler = useCallback(async () => {
|
||||
if (!initialValuesId) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
await PurchaseApi.delete(initialValuesId);
|
||||
deleteModal.closeModal();
|
||||
toast.success('Successfully delete Purchase Request!');
|
||||
setIsDeleteLoading(false);
|
||||
router.push('/purchase');
|
||||
}, [deleteModal, initialValuesId, router]);
|
||||
|
||||
return {
|
||||
deleteModal,
|
||||
purchaseRequestFormErrorMessage,
|
||||
isDeleteLoading,
|
||||
createPurchaseRequestHandler,
|
||||
updatePurchaseRequestHandler,
|
||||
deletePurchaseRequestClickHandler,
|
||||
confirmationModalDeleteClickHandler,
|
||||
};
|
||||
};
|
||||
Vendored
+3
-5
@@ -1,6 +1,5 @@
|
||||
import { BaseMetadata } from '@/types/api/api-general';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
|
||||
export type BasePurchase = {
|
||||
id: number;
|
||||
@@ -8,7 +7,6 @@ export type BasePurchase = {
|
||||
po_number: string;
|
||||
po_date: string;
|
||||
supplier: Supplier;
|
||||
warehouse: Warehouse[];
|
||||
credit_term: number;
|
||||
due_date: string;
|
||||
grand_total: number;
|
||||
@@ -24,10 +22,10 @@ export type CreatePurchaseRequestPayload = {
|
||||
credit_term: number;
|
||||
notes?: string | null;
|
||||
purchase_items: {
|
||||
warehouse_ids: number;
|
||||
warehouse_id: number;
|
||||
product_id: number;
|
||||
product_warehouse_id: number;
|
||||
total_qty: number;
|
||||
product_warehouse_id?: number | null;
|
||||
sub_qty: number;
|
||||
price: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user