refactor(FE-208,212): rename product_warehouse to warehouse and update related schema and form handling

This commit is contained in:
rstubryan
2025-11-19 16:21:46 +07:00
parent 8bda56e5d3
commit 14f216a352
2 changed files with 113 additions and 198 deletions
@@ -18,18 +18,13 @@ type PurchaseRequestFormSchemaType = {
label: string;
} | null;
location_id: number;
warehouse?: {
value: number;
label: string;
} | null;
warehouse_id: number;
notes: string | null;
items: {
product_warehouse?: {
warehouse?: {
value: number;
label: string;
} | null;
product_warehouse_id: number;
warehouse_id: number;
product?: {
value: number;
label: string;
@@ -40,11 +35,11 @@ type PurchaseRequestFormSchemaType = {
};
export type PurchaseItemSchema = {
product_warehouse?: {
warehouse?: {
value: number;
label: string;
} | null;
product_warehouse_id: number;
warehouse_id: number;
product?: {
value: number;
label: string;
@@ -55,14 +50,14 @@ export type PurchaseItemSchema = {
const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
Yup.object({
product_warehouse: Yup.object({
warehouse: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
product_warehouse_id: Yup.number()
.required('Produk wajib dipilih!')
.min(1, 'Produk wajib dipilih!')
.typeError('Produk wajib dipilih!'),
warehouse_id: Yup.number()
.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(),
@@ -107,14 +102,6 @@ export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSche
.required('Lokasi wajib dipilih!')
.min(1, 'Lokasi wajib dipilih!')
.typeError('Lokasi wajib dipilih!'),
warehouse: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}),
warehouse_id: Yup.number()
.required('Gudang wajib dipilih!')
.min(1, 'Gudang wajib dipilih!')
.typeError('Gudang wajib dipilih!'),
notes: Yup.string().nullable().default(null),
items: Yup.array()
.of(PurchaseItemObjectSchema)
@@ -154,13 +141,6 @@ export const getPurchaseRequestFormInitialValues = (
}
: null,
location_id: initialValues?.location?.id ?? 0,
warehouse: initialValues?.warehouse
? {
value: initialValues.warehouse.id,
label: initialValues.warehouse.name,
}
: undefined,
warehouse_id: initialValues?.warehouse?.id ?? 0,
notes: initialValues?.notes ?? null,
items: [],
});
@@ -32,7 +32,6 @@ import {
} from '@/services/api/master-data';
import { Supplier } from '@/types/api/master-data/supplier';
import { Product } from '@/types/api/master-data/product';
import { ProductWarehouseApi } from '@/services/api/inventory';
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
import { PurchaseRequestApi } from '@/services/api/purchase';
@@ -63,18 +62,15 @@ const PurchaseRequestForm = ({
useState('');
// ===== TYPE DEFINITIONS =====
interface ProductWarehouseOptionType extends OptionType {
product_id: number;
product_warehouse_id: number;
warehouse_id: number;
warehouse_name: string;
qty: number;
interface ProductOptionType {
value: number;
label: string;
}
// ===== UTILITY FUNCTIONS =====
const isRepeaterInputError = (
idx: number,
field: 'product_warehouse_id' | 'product_id' | 'qty'
field: 'warehouse_id' | 'product_id' | 'qty'
): { isError: boolean; errorMessage: string } => {
if (!formik.touched.items || !Array.isArray(formik.touched.items)) {
return {
@@ -83,7 +79,13 @@ const PurchaseRequestForm = ({
};
}
const touchedField = formik.touched.items[idx]?.[field];
const touchedField = (
formik.touched.items[idx] as Partial<{
warehouse_id: boolean;
product_id: boolean;
qty: boolean;
}>
)?.[field];
const errorItem = formik.errors.items?.[idx] as
| Record<string, string>
| undefined;
@@ -142,7 +144,6 @@ const PurchaseRequestForm = ({
// ===== SELECT INPUT DATA =====
const {
inputValue: supplierSelectInputValue,
setInputValue: setSupplierSelectInputValue,
options: supplierOptions,
isLoadingOptions: isLoadingSuppliers,
@@ -150,7 +151,6 @@ const PurchaseRequestForm = ({
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
const {
inputValue: areaSelectInputValue,
setInputValue: setAreaSelectInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreas,
@@ -176,27 +176,23 @@ const PurchaseRequestForm = ({
: PurchaseRequestFormSchema,
validateOnChange: true,
validateOnBlur: true,
validateOnMount: false,
enableReinitialize: true,
onSubmit: async (values) => {
const payload: CreatePurchaseRequestPayload = {
supplier_id:
typeof values.supplier_id === 'string'
? parseInt(values.supplier_id) || 0
: values.supplier_id || 0,
credit_term: values.credit_term || 0,
credit_term:
typeof values.credit_term === 'string'
? parseInt(values.credit_term) || 0
: values.credit_term || 0,
notes: values.notes || '',
items: (values.items || []).map((item) => ({
warehouse_id:
typeof values.warehouse_id === 'string'
? parseInt(values.warehouse_id) || 0
: values.warehouse_id || 0,
product_id:
typeof item.product_id === 'string'
? parseInt(item.product_id) || 0
: item.product_id || 0,
qty:
typeof item.qty === 'string'
? parseFloat(item.qty) || 0
: item.qty || 0,
warehouse_id: Number(item.warehouse_id) || 0,
product_id: Number(item.product_id) || 0,
qty: Number(item.qty) || 0,
})),
};
@@ -215,57 +211,22 @@ const PurchaseRequestForm = ({
});
// ===== API DATA FETCHING =====
const productWarehousesUrl = useMemo(() => {
if (!formik.values.warehouse_id || formik.values.warehouse_id === 0) {
return null;
}
const params = new URLSearchParams({
warehouse_id: formik.values.warehouse_id.toString(),
});
return `${ProductWarehouseApi.basePath}?${params.toString()}`;
}, [formik.values.warehouse_id]);
const { data: productWarehouses, isLoading: isLoadingProductWarehouses } =
useSWR(productWarehousesUrl, ProductWarehouseApi.getAllFetcher);
const getProductWarehouseOptionsForItem = useCallback(() => {
if (!isResponseSuccess(productWarehouses)) return [];
return (
productWarehouses?.data.map((pw) => ({
value: pw.id,
label: pw.product.name,
product_id: pw.product.id,
product_warehouse_id: pw.id,
warehouse_id: pw.warehouse.id,
warehouse_name: pw.warehouse.name,
qty: pw.quantity,
})) || []
);
}, [productWarehouses]);
const productUrl = useMemo(() => {
const productIds =
formik.values.items
?.filter(
(item) => item.product_id && typeof item.product_id === 'number'
)
.map((item) => item.product_id as number) || [];
return productIds.length > 0
? `${ProductApi.basePath}?${new URLSearchParams({
id: productIds.join(','),
}).toString()}`
: null;
}, [formik.values.items]);
const { data: productsResponse } = useSWR(
productUrl,
const { data: productsResponse, isLoading: isLoadingProducts } = useSWR(
`${ProductApi.basePath}`,
ProductApi.getAllFetcher
);
const productOptions = useMemo(() => {
if (!isResponseSuccess(productsResponse)) return [];
return (
productsResponse?.data.map((product: Product) => ({
value: product.id,
label: product.name,
})) || []
);
}, [productsResponse]);
const productData = useMemo(() => {
if (!isResponseSuccess(productsResponse)) return {};
@@ -340,19 +301,18 @@ const PurchaseRequestForm = ({
);
}, [warehouses]);
// Purchase Items Handlers
const addPurchaseItem = () => {
const newPurchaseItems = [
const newItems = [
...(formik.values.items || []),
{
product_warehouse: null,
product_warehouse_id: null,
warehouse: null,
warehouse_id: 0,
product: null,
product_id: '',
qty: '',
product_id: 0,
qty: 0,
},
];
formik.setFieldValue('items', newPurchaseItems);
formik.setFieldValue('items', newItems);
};
const removePurchaseItem = (idx: number) => {
@@ -445,9 +405,25 @@ const PurchaseRequestForm = ({
supplierData.due_date.toString()
);
}
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, 0);
formik.setFieldValue(`items.${idx}.qty`, 0);
});
}
} else {
formik.setFieldTouched('credit_term', false);
formik.setFieldValue('credit_term', '');
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, 0);
formik.setFieldValue(`items.${idx}.qty`, 0);
});
}
}
}}
options={supplierOptions}
@@ -508,16 +484,8 @@ const PurchaseRequestForm = ({
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
`items.${idx}.product_warehouse`,
null
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
null
);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
formik.setFieldValue(`items.${idx}.product_id`, 0);
});
}
}}
@@ -553,16 +521,8 @@ const PurchaseRequestForm = ({
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
`items.${idx}.product_warehouse`,
null
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
null
);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
formik.setFieldValue(`items.${idx}.product_id`, 0);
});
}
}}
@@ -691,45 +651,42 @@ const PurchaseRequestForm = ({
? 'Pilih Gudang...'
: 'Pilih Area dan Lokasi terlebih dahulu'
}
value={formik.values.warehouse}
value={item.warehouse}
onChange={(val) => {
const warehouse = val as OptionType | null;
formik.setFieldTouched('warehouse_id', true);
formik.setFieldValue(
'warehouse_id',
(warehouse as OptionType)?.value || 0
const warehouseId =
(warehouse as OptionType)?.value || 0;
formik.setFieldTouched(
`items.${idx}.warehouse`,
true
);
formik.setFieldTouched('warehouse', true);
formik.setFieldValue('warehouse', warehouse);
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
`items.${idx}.product_warehouse`,
null
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
null
);
formik.setFieldValue(
`items.${idx}.product`,
null
);
formik.setFieldValue(
`items.${idx}.product_id`,
''
);
});
}
formik.setFieldValue(
`items.${idx}.warehouse`,
warehouse
);
formik.setFieldTouched(
`items.${idx}.warehouse_id`,
true
);
formik.setFieldValue(
`items.${idx}.warehouse_id`,
warehouseId
);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, 0);
}}
options={warehouseOptions}
onInputChange={setWarehouseSelectInputValue}
isLoading={isLoadingWarehouses}
isError={
formik.touched.warehouse_id &&
Boolean(formik.errors.warehouse_id)
isRepeaterInputError(idx, 'warehouse_id').isError
}
errorMessage={
isRepeaterInputError(idx, 'warehouse_id')
.errorMessage
}
errorMessage={formik.errors.warehouse_id as string}
isDisabled={
type === 'detail' ||
!formik.values.area_id ||
@@ -746,32 +703,20 @@ const PurchaseRequestForm = ({
<td>
<SelectInput
required
value={item.product_warehouse}
key={`product-warehouse-${idx}-${formik.values.warehouse_id}`}
value={item.product ?? undefined}
onChange={(val) => {
const productWarehouse =
val as ProductWarehouseOptionType | null;
formik.setFieldTouched(
`items.${idx}.product_warehouse`,
true
);
formik.setFieldValue(
`items.${idx}.product_warehouse`,
productWarehouse
);
formik.setFieldTouched(
`items.${idx}.product_warehouse_id`,
true
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
(productWarehouse as ProductWarehouseOptionType)
?.value || 0
);
const product = val as ProductOptionType | null;
const productId =
(productWarehouse as ProductWarehouseOptionType)
?.product_id || 0;
(product as ProductOptionType)?.value || 0;
formik.setFieldTouched(
`items.${idx}.product`,
true
);
formik.setFieldValue(
`items.${idx}.product`,
product
);
formik.setFieldTouched(
`items.${idx}.product_id`,
true
@@ -781,27 +726,17 @@ const PurchaseRequestForm = ({
productId
);
}}
options={getProductWarehouseOptionsForItem()}
isLoading={isLoadingProductWarehouses}
options={productOptions}
isLoading={isLoadingProducts}
isError={
isRepeaterInputError(idx, 'product_warehouse_id')
.isError
isRepeaterInputError(idx, 'product_id').isError
}
errorMessage={
isRepeaterInputError(idx, 'product_warehouse_id')
.errorMessage
}
isDisabled={
type === 'detail' || !formik.values.warehouse_id
}
isClearable={
type !== 'detail' && !!formik.values.warehouse_id
}
placeholder={
!formik.values.warehouse_id
? 'Pilih Gudang terlebih dahulu'
: 'Pilih Produk'
isRepeaterInputError(idx, 'product_id').errorMessage
}
isDisabled={type === 'detail'}
isClearable={type !== 'detail'}
placeholder='Pilih Produk'
className={{
wrapper: 'min-w-32',
}}
@@ -841,7 +776,7 @@ const PurchaseRequestForm = ({
).toLocaleString('en-US')
: ''
}
onChange={(e) => {}}
onChange={() => {}}
onBlur={formik.handleBlur}
type='text'
className={{