mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE-208,212): update PurchaseRequestForm schema and validation, enhance credit term handling and improve error messages for required fields
This commit is contained in:
@@ -17,7 +17,7 @@ type PurchaseRequestFormSchemaType = {
|
||||
label: string;
|
||||
} | null;
|
||||
location_id: number;
|
||||
credit_term: number | string;
|
||||
credit_term: number | string | null;
|
||||
notes: string | null;
|
||||
purchase_items: {
|
||||
warehouse?: {
|
||||
@@ -65,29 +65,38 @@ const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
warehouse_id: Yup.number()
|
||||
.required('Warehouse wajib diisi!')
|
||||
.min(1, 'Warehouse wajib diisi!')
|
||||
.typeError('Warehouse harus berupa angka!'),
|
||||
.required('Gudang wajib dipilih!')
|
||||
.min(1, 'Gudang wajib dipilih!')
|
||||
.typeError('Gudang wajib dipilih'),
|
||||
product: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
product_id: Yup.number()
|
||||
.required('Produk wajib diisi!')
|
||||
.min(1, 'Produk wajib diisi!')
|
||||
.typeError('Produk harus berupa angka!'),
|
||||
.required('Produk wajib dipilih!')
|
||||
.min(1, 'Produk wajib dipilih!')
|
||||
.typeError('Produk wajib dipilih!'),
|
||||
product_warehouse: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
product_warehouse_id: Yup.number()
|
||||
.required('Product Warehouse wajib diisi!')
|
||||
.min(0.001, 'Product Warehouse tidak boleh negatif!')
|
||||
.typeError('Product Warehouse harus berupa angka!'),
|
||||
sub_qty: Yup.number()
|
||||
.required('Produk wajib dipilih!')
|
||||
.min(1, 'Produk wajib dipilih!')
|
||||
.typeError('Produk wajib dipilih!'),
|
||||
sub_qty: Yup.mixed<string | number>()
|
||||
.required('Sub Qty wajib diisi!')
|
||||
.min(0.001, 'Sub Qty tidak boleh negatif!')
|
||||
.typeError('Sub Qty harus berupa angka!'),
|
||||
.test(
|
||||
'is-valid-sub-qty',
|
||||
'Kuantitas harus berupa angka lebih dari 0!',
|
||||
function (value) {
|
||||
if (value === '' || value === null || value === undefined)
|
||||
return false;
|
||||
const numValue =
|
||||
typeof value === 'string' ? parseFloat(value) : value;
|
||||
return !isNaN(numValue) && numValue > 0;
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSchemaType> =
|
||||
@@ -97,35 +106,50 @@ export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSche
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
supplier_id: Yup.number()
|
||||
.required('Supplier wajib diisi!')
|
||||
.min(1, 'Supplier wajib diisi!')
|
||||
.typeError('Supplier wajib diisi!'),
|
||||
.required('Supplier wajib dipilih!')
|
||||
.test('is-valid-supplier', 'Supplier wajib dipilih!', function (value) {
|
||||
if (!this.parent.supplier) return true;
|
||||
return Boolean(value && value > 0);
|
||||
})
|
||||
.typeError('Supplier wajib dipilih!'),
|
||||
area: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
area_id: Yup.number()
|
||||
.required('Area wajib diisi!')
|
||||
.test('is-valid-area', 'Area wajib diisi!', function (value) {
|
||||
.required('Area wajib dipilih!')
|
||||
.test('is-valid-area', 'Area wajib dipilih!', function (value) {
|
||||
if (!this.parent.area) return true;
|
||||
return Boolean(value && value > 0);
|
||||
})
|
||||
.typeError('Area wajib diisi!'),
|
||||
.typeError('Area wajib dipilih!'),
|
||||
location: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
location_id: Yup.number()
|
||||
.required('Lokasi wajib diisi!')
|
||||
.test('is-valid-location', 'Lokasi wajib diisi!', function (value) {
|
||||
.required('Lokasi wajib dipilih!')
|
||||
.test('is-valid-location', 'Lokasi wajib dipilih!', function (value) {
|
||||
if (!this.parent.location) return true;
|
||||
return Boolean(value && value > 0);
|
||||
})
|
||||
.typeError('Lokasi wajib diisi!'),
|
||||
credit_term: Yup.number()
|
||||
.required('Termin kredit wajib diisi!')
|
||||
.min(1, 'Termin kredit tidak boleh negatif!')
|
||||
.typeError('Termin kredit harus berupa angka!'),
|
||||
.typeError('Lokasi wajib dipilih!'),
|
||||
credit_term: Yup.lazy((value, context) => {
|
||||
const supplier_id = context.parent.supplier_id;
|
||||
const hasSupplier = supplier_id && supplier_id > 0;
|
||||
|
||||
if (!hasSupplier) {
|
||||
return Yup.mixed<string | number>()
|
||||
.nullable()
|
||||
.default(null)
|
||||
.transform(() => null);
|
||||
}
|
||||
|
||||
return Yup.number()
|
||||
.required('Jumlah hari jatuh tempo wajib diisi!')
|
||||
.min(1, 'Jumlah hari jatuh tempo minimal 1 hari!')
|
||||
.typeError('Jumlah hari jatuh tempo harus berupa angka!');
|
||||
}),
|
||||
notes: Yup.string().nullable().default(null),
|
||||
purchase_items: Yup.array()
|
||||
.of(PurchaseItemObjectSchema)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import useSWR from 'swr';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -8,6 +8,7 @@ import { Icon } from '@iconify/react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Button from '@/components/Button';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
LocationApi,
|
||||
WarehouseApi,
|
||||
} from '@/services/api/master-data';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||
import { PurchaseApi } from '@/services/api/purchasing';
|
||||
@@ -96,6 +98,15 @@ const PurchaseRequestForm = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getSupplierById = (supplierId: number): Supplier | null => {
|
||||
if (!isResponseSuccess(supplierRawData)) return null;
|
||||
return (
|
||||
supplierRawData?.data.find(
|
||||
(supplier: Supplier) => supplier.id === supplierId
|
||||
) || null
|
||||
);
|
||||
};
|
||||
|
||||
// ===== FORM HANDLERS =====
|
||||
const createPurchaseRequestHandler = useCallback(
|
||||
async (payload: CreatePurchaseRequestPayload) => {
|
||||
@@ -155,7 +166,8 @@ const PurchaseRequestForm = ({
|
||||
setInputValue: setSupplierSelectInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSuppliers,
|
||||
} = useSelect(SupplierApi.basePath, 'id', 'name', 'search');
|
||||
rawData: supplierRawData,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const {
|
||||
inputValue: areaSelectInputValue,
|
||||
@@ -353,6 +365,20 @@ const PurchaseRequestForm = ({
|
||||
formik.setFieldValue('supplier', supplier);
|
||||
formik.setFieldTouched('supplier_id', true);
|
||||
formik.setFieldValue('supplier_id', (supplier as OptionType)?.value || 0);
|
||||
|
||||
if (supplier?.value) {
|
||||
const supplierId =
|
||||
typeof supplier.value === 'string'
|
||||
? parseInt(supplier.value)
|
||||
: supplier.value;
|
||||
const supplierData = getSupplierById(supplierId);
|
||||
if (supplierData?.due_date) {
|
||||
formik.setFieldTouched('credit_term', true);
|
||||
formik.setFieldValue('credit_term', supplierData.due_date.toString());
|
||||
}
|
||||
} else {
|
||||
formik.setFieldValue('credit_term', '');
|
||||
}
|
||||
};
|
||||
|
||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -509,15 +535,15 @@ const PurchaseRequestForm = ({
|
||||
onInputChange={setSupplierSelectInputValue}
|
||||
isLoading={isLoadingSuppliers}
|
||||
isError={
|
||||
formik.touched.supplier_id &&
|
||||
Boolean(formik.errors.supplier_id)
|
||||
formik.touched.supplier && Boolean(formik.errors.supplier_id)
|
||||
}
|
||||
errorMessage={formik.errors.supplier_id as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
|
||||
<NumberInput
|
||||
required={!!formik.values.supplier_id}
|
||||
label='Jatuh tempo (hari)'
|
||||
name='credit_term'
|
||||
value={formik.values.credit_term || ''}
|
||||
@@ -528,9 +554,15 @@ const PurchaseRequestForm = ({
|
||||
Boolean(formik.errors.credit_term)
|
||||
}
|
||||
errorMessage={formik.errors.credit_term as string}
|
||||
readOnly={type === 'detail'}
|
||||
type='number'
|
||||
placeholder='Masukkan Credit Term'
|
||||
readOnly={type === 'detail' || !formik.values.supplier_id}
|
||||
disabled={type === 'detail' || !formik.values.supplier_id}
|
||||
allowNegative={false}
|
||||
decimalScale={0}
|
||||
placeholder={
|
||||
!formik.values.supplier_id
|
||||
? 'Pilih Vendor terlebih dahulu'
|
||||
: 'Masukkan jumlah hari jatuh tempo'
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -542,9 +574,7 @@ const PurchaseRequestForm = ({
|
||||
options={areaOptions}
|
||||
onInputChange={setAreaSelectInputValue}
|
||||
isLoading={isLoadingAreas}
|
||||
isError={
|
||||
formik.touched.area && Boolean(formik.errors.area_id)
|
||||
}
|
||||
isError={formik.touched.area && Boolean(formik.errors.area_id)}
|
||||
errorMessage={formik.errors.area_id as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
|
||||
Reference in New Issue
Block a user