refactor(FE): Allow null for select fields and track id fields

This commit is contained in:
rstubryan
2026-01-12 14:12:12 +07:00
parent 8516929056
commit bf834cf79b
3 changed files with 90 additions and 38 deletions
@@ -7,18 +7,19 @@ type ExpenseFormSchemaType = {
category?: { category?: {
value: 'BOP' | 'NON-BOP'; value: 'BOP' | 'NON-BOP';
label: 'BOP' | 'NON-BOP'; label: 'BOP' | 'NON-BOP';
}; } | null;
location?: { location?: {
value: number; value: number;
label: string; label: string;
}; } | null;
location_id: number; location_id: number;
transaction_date?: string; transaction_date?: string;
kandangs?: { id?: number; name?: string }[]; kandangs?: { id?: number; name?: string }[];
supplier?: { supplier?: {
value: number; value: number;
label: string; label: string;
}; } | null;
supplier_id: number;
existing_documents?: { id: number; name: string; url: string }[]; existing_documents?: { id: number; name: string; url: string }[];
deleted_documents?: number[]; deleted_documents?: number[];
documents?: File[]; documents?: File[];
@@ -28,7 +29,8 @@ type ExpenseFormSchemaType = {
nonstock?: { nonstock?: {
value: number; value: number;
label: string; label: string;
}; } | null;
nonstock_id?: number;
quantity?: number; quantity?: number;
price?: number; price?: number;
notes?: string; notes?: string;
@@ -41,16 +43,24 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
category: Yup.object({ category: Yup.object({
value: Yup.string().oneOf(['BOP', 'NON-BOP']).required(), value: Yup.string().oneOf(['BOP', 'NON-BOP']).required(),
label: Yup.string().oneOf(['BOP', 'NON-BOP']).required(), label: Yup.string().oneOf(['BOP', 'NON-BOP']).required(),
}).required('Kategori wajib diisi!'), })
.nullable()
.optional(),
location: Yup.object({ location: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).required('Lokasi wajib diisi!'), })
.nullable()
.optional(),
location_id: Yup.number().min(1).required('Lokasi wajib diisi!'), location_id: Yup.number()
.required('Lokasi wajib diisi!')
.min(1, 'Lokasi wajib diisi!')
.typeError('Lokasi wajib diisi!'),
transaction_date: Yup.string().required('Tanggal transaksi wajib diisi!'), transaction_date: Yup.string().required('Tanggal transaksi wajib diisi!'),
kandangs: Yup.array() kandangs: Yup.array()
.of( .of(
Yup.object({ Yup.object({
@@ -63,28 +73,28 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
supplier: Yup.object({ supplier: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).required('Vendor wajib diisi!'), })
.nullable()
.optional(),
existing_documents: Yup.array().of( supplier_id: Yup.number()
Yup.object({ .required('Vendor wajib diisi!')
id: Yup.number().required(), .min(1, 'Vendor wajib diisi!')
name: Yup.string().required(), .typeError('Vendor wajib diisi!'),
url: Yup.string().required(),
}) existing_documents: Yup.array()
), .of(
Yup.object({
id: Yup.number().required(),
name: Yup.string().required(),
url: Yup.string().required(),
})
)
.optional(),
deleted_documents: Yup.array().of(Yup.number().required()).optional(), deleted_documents: Yup.array().of(Yup.number().required()).optional(),
documents: Yup.array() documents: Yup.array().of(Yup.mixed<File>().required()).optional(),
.of(
Yup.mixed<File>()
.required()
.test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => {
if (!value || !(value instanceof File)) return true;
return value.size <= 5 * 1024 * 1024;
})
)
.optional(),
expense_nonstocks: Yup.array() expense_nonstocks: Yup.array()
.of( .of(
@@ -96,9 +106,17 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
nonstock: Yup.object({ nonstock: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).required('Nonstock wajib diisi!'), }).nullable(),
quantity: Yup.number().required('Total kuantitas wajib diisi!'), nonstock_id: Yup.number()
price: Yup.number().required('Harga satuan wajib diisi!'), .required('Nonstock wajib diisi!')
.min(1, 'Nonstock wajib diisi!')
.typeError('Nonstock wajib diisi!'),
quantity: Yup.number()
.required('Total kuantitas wajib diisi!')
.typeError('Total kuantitas wajib diisi!'),
price: Yup.number()
.required('Harga satuan wajib diisi!')
.typeError('Harga satuan wajib diisi!'),
notes: Yup.string(), notes: Yup.string(),
}) })
) )
@@ -142,13 +160,13 @@ export const getExpenseFormInitialValues = (
value: initialValues.category, value: initialValues.category,
label: initialValues.category, label: initialValues.category,
} }
: undefined, : null,
location: initialValues?.location location: initialValues?.location
? { ? {
value: initialValues.location.id, value: initialValues.location.id,
label: initialValues.location.name, label: initialValues.location.name,
} }
: undefined, : null,
location_id: Number(initialValues?.location.id || 0), location_id: Number(initialValues?.location.id || 0),
transaction_date: initialValues?.transaction_date transaction_date: initialValues?.transaction_date
? formatDate(initialValues.transaction_date, 'YYYY-MM-DD') ? formatDate(initialValues.transaction_date, 'YYYY-MM-DD')
@@ -162,7 +180,8 @@ export const getExpenseFormInitialValues = (
value: initialValues.supplier.id, value: initialValues.supplier.id,
label: initialValues.supplier.name, label: initialValues.supplier.name,
} }
: undefined, : null,
supplier_id: initialValues?.supplier?.id ?? 0,
existing_documents: initialValues?.documents?.map((doc) => { existing_documents: initialValues?.documents?.map((doc) => {
const path = doc.path.startsWith('/') ? doc.path.slice(1) : doc.path; const path = doc.path.startsWith('/') ? doc.path.slice(1) : doc.path;
return { return {
@@ -182,12 +201,25 @@ export const getExpenseFormInitialValues = (
value: expenseItem.nonstock.id, value: expenseItem.nonstock.id,
label: expenseItem.nonstock.name, label: expenseItem.nonstock.name,
}, },
nonstock_id: expenseItem.nonstock.id,
quantity: expenseItem.qty, quantity: expenseItem.qty,
price: expenseItem.price, price: expenseItem.price,
notes: expenseItem.note, notes: expenseItem.note,
})) }))
: [], : [],
})) }))
: [], : [
{
cost_items: [
{
nonstock: null,
nonstock_id: 0,
quantity: undefined,
price: undefined,
notes: '',
},
],
},
],
}; };
}; };
@@ -204,7 +204,8 @@ const ExpenseRequestForm = ({
{ {
cost_items: [ cost_items: [
{ {
nonstock: undefined, nonstock: null,
nonstock_id: 0,
quantity: undefined, quantity: undefined,
price: undefined, price: undefined,
notes: '', notes: '',
@@ -226,7 +227,8 @@ const ExpenseRequestForm = ({
{ {
cost_items: [ cost_items: [
{ {
nonstock: undefined, nonstock: null,
nonstock_id: 0,
quantity: undefined, quantity: undefined,
price: undefined, price: undefined,
notes: '', notes: '',
@@ -251,7 +253,8 @@ const ExpenseRequestForm = ({
kandang_id: kandangItem.id, kandang_id: kandangItem.id,
cost_items: existingExpenseNonstock?.cost_items || [ cost_items: existingExpenseNonstock?.cost_items || [
{ {
nonstock: undefined, nonstock: null,
nonstock_id: 0,
quantity: undefined, quantity: undefined,
price: undefined, price: undefined,
notes: '', notes: '',
@@ -266,10 +269,20 @@ const ExpenseRequestForm = ({
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => { const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldTouched('supplier', true); formik.setFieldTouched('supplier', true);
formik.setFieldValue('supplier', val); formik.setFieldValue('supplier', val);
const supplierId = Array.isArray(val) ? val[0]?.value : val?.value;
formik.setFieldValue('supplier_id', supplierId ?? 0);
}; };
const requestDocumentsChangeHandler = (val: File[]) => { const requestDocumentsChangeHandler = (val: File[]) => {
formik.setFieldTouched('documents', true); formik.setFieldTouched('documents', true);
const invalidFiles = val.filter((file) => file.size > 5 * 1024 * 1024);
if (invalidFiles.length > 0) {
toast.error('Ukuran dokumen maksimal 5 MB!');
return;
}
formik.setFieldValue('documents', val); formik.setFieldValue('documents', val);
}; };
@@ -585,7 +598,7 @@ const ExpenseRequestForm = ({
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -25,7 +25,7 @@ interface ExpenseRequestKandangDetailExpenseProps {
location?: { location?: {
value: number; value: number;
label: string; label: string;
}; } | null;
className?: { className?: {
wrapper?: string; wrapper?: string;
}; };
@@ -59,13 +59,20 @@ const ExpenseRequestKandangDetailExpense: React.FC<
`expense_nonstocks[${kandangExpenseIdx}].cost_items[${expenseIdx}].nonstock`, `expense_nonstocks[${kandangExpenseIdx}].cost_items[${expenseIdx}].nonstock`,
val val
); );
const nonstockId = Array.isArray(val) ? val[0]?.value : val?.value;
formik.setFieldValue(
`expense_nonstocks[${kandangExpenseIdx}].cost_items[${expenseIdx}].nonstock_id`,
nonstockId ?? 0
);
}; };
const addExpenseItemHandler = (kandangExpenseIdx: number) => { const addExpenseItemHandler = (kandangExpenseIdx: number) => {
const newExpensesValue = [ const newExpensesValue = [
...formik.values.expense_nonstocks[kandangExpenseIdx].cost_items, ...formik.values.expense_nonstocks[kandangExpenseIdx].cost_items,
{ {
nonstock: undefined, nonstock: null,
nonstock_id: 0,
price: undefined, price: undefined,
quantity: undefined, quantity: undefined,
notes: '', notes: '',