From 2bf0f2874ee7994dc65901090e3445fdd7a959bb Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 30 Dec 2025 19:28:38 +0700 Subject: [PATCH] refactor(FE-435): Allow realizations without kandang --- .../form/ExpenseRealizationForm.schema.ts | 21 +- .../expense/form/ExpenseRealizationForm.tsx | 96 +++--- ...ExpenseRealizationKandangDetailExpense.tsx | 276 ++++++++++-------- .../expense/form/ExpenseRequestForm.schema.ts | 7 +- .../pages/expense/form/ExpenseRequestForm.tsx | 6 +- 5 files changed, 219 insertions(+), 187 deletions(-) diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts b/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts index 77db761c..f22e72ed 100644 --- a/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts @@ -12,7 +12,7 @@ type ExpenseRealizationFormSchemaType = { label: string; }; realization_date?: string; - kandangs?: { id: number; name: string }[]; + kandangs?: { id?: number; name?: string }[]; supplier?: { value: number; label: string; @@ -20,7 +20,7 @@ type ExpenseRealizationFormSchemaType = { existing_documents?: { name: string; url: string }[]; documents?: File[]; realizations: { - kandang_id: number; + kandang_id?: number; cost_items: { nonstock?: { value: number; @@ -49,12 +49,11 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema { - formik.setFieldTouched('kandangs', true); - formik.setFieldValue('kandangs', kandangs); - - const newRealizations = [...(formik.values.realizations ?? [])]; - - // add new realizations - kandangs.forEach((kandangItem) => { - if (!kandangItem.id) return; - - const isKandangExistInRealization = newRealizations.find( - (realizationItem) => realizationItem.kandang_id === kandangItem.id - ); - - if (isKandangExistInRealization) return; - - newRealizations.push({ - kandang_id: kandangItem.id, + // Auto-create realization item for location (without kandang) + formik.setFieldValue('realizations', [ + { cost_items: [ { nonstock: undefined, @@ -181,29 +162,57 @@ const ExpenseRealizationForm = ({ notes: '', }, ], + }, + ]); + }; + + const kandangsChangeHandler = ( + kandangs: { id?: number; name?: string }[] + ) => { + formik.setFieldTouched('kandangs', true); + formik.setFieldValue('kandangs', kandangs); + + // If no kandangs selected, create realization item for location + if (kandangs.length === 0) { + formik.setFieldValue('realizations', [ + { + cost_items: [ + { + nonstock: undefined, + quantity: undefined, + price: undefined, + notes: '', + }, + ], + }, + ]); + return; + } + + // Start with empty array when kandangs are selected + const newRealizations: typeof formik.values.realizations = []; + + // add new realizations for each kandang + kandangs.forEach((kandangItem) => { + if (!kandangItem.id) return; + + const existingRealization = formik.values.realizations?.find( + (realizationItem) => realizationItem.kandang_id === kandangItem.id + ); + + newRealizations.push({ + kandang_id: kandangItem.id, + cost_items: existingRealization?.cost_items || [ + { + nonstock: undefined, + quantity: undefined, + price: undefined, + notes: '', + }, + ], }); }); - // prune realizations - const kandangIds = new Set( - kandangs - .map((kandang) => kandang.id) - .filter((id): id is number => id !== undefined) - ); - const deletedRealizationsIdx: number[] = []; - - newRealizations.forEach((realization, idx) => { - const isRealizationValid = kandangIds.has(realization.kandang_id); - - if (!isRealizationValid) { - deletedRealizationsIdx.push(idx); - } - }); - - deletedRealizationsIdx.forEach((deletedRealizationIdx) => { - newRealizations.splice(deletedRealizationIdx, 1); - }); - formik.setFieldValue('realizations', newRealizations); }; @@ -346,7 +355,10 @@ const ExpenseRealizationForm = ({ )} ; + supplierId?: number; + location?: { + value: number; + label: string; + }; className?: { wrapper?: string; }; @@ -25,12 +30,18 @@ interface ExpenseRealizationKandangDetailExpenseProps { const ExpenseRealizationKandangDetailExpense: React.FC< ExpenseRealizationKandangDetailExpenseProps -> = ({ type, formik, className }) => { +> = ({ type, formik, supplierId, location, className }) => { const { setInputValue: setNonstockInputValue, options: nonstockOptions, isLoadingOptions: isLoadingNonstockOptions, - } = useSelect(NonstockApi.basePath, 'id', 'name'); + } = useSelect( + NonstockApi.basePath, + 'id', + 'name', + 'search', + supplierId ? { supplier_id: String(supplierId) } : undefined + ); const nonstockChangeHandler = ( kandangExpenseIdx: number, @@ -82,140 +93,155 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
- {formik.values.realizations.length === 0 && ( + {!formik.values.supplier?.value && (

- Pilih kandang terlebih dahulu! + Pilih supplier terlebih dahulu!

)} - {formik.values.realizations.map((kandangExpense, kandangExpenseIdx) => { - const kandangName = formik.values.kandangs?.find( - (kandang) => kandang.id === kandangExpense.kandang_id - ); + {formik.values.realizations.length === 0 && + formik.values.supplier?.value && ( +
+

+ Belum ada item biaya. Silakan pilih lokasi terlebih dahulu. +

+
+ )} - return ( - kandangName?.name && ( -
-
-
- Biaya {kandangName?.name} -
+ {formik.values.realizations.length > 0 && + formik.values.supplier?.value && + formik.values.realizations.map( + (kandangExpense, kandangExpenseIdx) => { + const kandangName = kandangExpense.kandang_id + ? formik.values.kandangs?.find( + (kandang) => kandang.id === kandangExpense.kandang_id + ) + : null; -
- - - - - - - - - + return ( + (kandangName?.name || !kandangExpense.kandang_id) && ( +
+
+
+ Biaya {kandangName?.name || location?.label || 'Umum'} +
-
- {kandangExpense.cost_items.map( - (expenseItem, expenseIdx) => ( - - - - - - - - +
+
NonstockTotal KuantitasHarga SatuanCatatan
- { - nonstockChangeHandler( - kandangExpenseIdx, - expenseIdx, - val - ); - }} - options={nonstockOptions} - isLoading={isLoadingNonstockOptions} - onInputChange={setNonstockInputValue} - className={{ wrapper: 'min-w-48' }} - isDisabled - /> - - - - - Rp - - } - className={{ wrapper: 'min-w-24' }} - /> - - -
+ + + + + + - ) - )} - -
NonstockTotal KuantitasHarga SatuanCatatan
+ + + + {kandangExpense.cost_items.map( + (expenseItem, expenseIdx) => ( + + + { + nonstockChangeHandler( + kandangExpenseIdx, + expenseIdx, + val + ); + }} + options={nonstockOptions} + isLoading={isLoadingNonstockOptions} + onInputChange={setNonstockInputValue} + className={{ wrapper: 'min-w-48' }} + isDisabled + /> + + + + + + + + + Rp + + } + className={{ wrapper: 'min-w-24' }} + /> + + + + + + + ) + )} + + +
+
-
- - ) - ); - })} + ) + ); + } + )} ); diff --git a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts index 4a4c921b..f0dfecd7 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts @@ -22,7 +22,7 @@ type ExpenseFormSchemaType = { deleted_documents?: number[]; documents?: File[]; expense_nonstocks: { - kandang_id?: number | null; + kandang_id?: number; cost_items: { nonstock?: { value: number; @@ -79,10 +79,7 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema = expense_nonstocks: Yup.array() .of( Yup.object({ - kandang_id: Yup.number() - .min(1, 'Wajib memilih kandang!') - .nullable() - .optional(), + kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').optional(), cost_items: Yup.array() .of( Yup.object({ diff --git a/src/components/pages/expense/form/ExpenseRequestForm.tsx b/src/components/pages/expense/form/ExpenseRequestForm.tsx index 78646e37..60e55397 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.tsx +++ b/src/components/pages/expense/form/ExpenseRequestForm.tsx @@ -122,7 +122,7 @@ const ExpenseRequestForm = ({ })), }; - return expenseNonstock.kandang_id !== null + return expenseNonstock.kandang_id ? { ...basePayload, kandang_id: expenseNonstock.kandang_id } : basePayload; }), @@ -151,7 +151,7 @@ const ExpenseRequestForm = ({ })), }; - return expenseNonstock.kandang_id !== null + return expenseNonstock.kandang_id ? { ...basePayload, kandang_id: expenseNonstock.kandang_id } : basePayload; } @@ -199,7 +199,6 @@ const ExpenseRequestForm = ({ // Auto-create expense item for location (without kandang) formik.setFieldValue('expense_nonstocks', [ { - kandang_id: null, cost_items: [ { nonstock: undefined, @@ -222,7 +221,6 @@ const ExpenseRequestForm = ({ if (kandangs.length === 0) { formik.setFieldValue('expense_nonstocks', [ { - kandang_id: null, cost_items: [ { nonstock: undefined,