refactor(FE-435): Allow realizations without kandang

This commit is contained in:
rstubryan
2025-12-30 19:28:38 +07:00
parent a81a61135f
commit 2bf0f2874e
5 changed files with 219 additions and 187 deletions
@@ -12,7 +12,7 @@ type ExpenseRealizationFormSchemaType = {
label: string; label: string;
}; };
realization_date?: string; realization_date?: string;
kandangs?: { id: number; name: string }[]; kandangs?: { id?: number; name?: string }[];
supplier?: { supplier?: {
value: number; value: number;
label: string; label: string;
@@ -20,7 +20,7 @@ type ExpenseRealizationFormSchemaType = {
existing_documents?: { name: string; url: string }[]; existing_documents?: { name: string; url: string }[];
documents?: File[]; documents?: File[];
realizations: { realizations: {
kandang_id: number; kandang_id?: number;
cost_items: { cost_items: {
nonstock?: { nonstock?: {
value: number; value: number;
@@ -49,12 +49,11 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema<ExpenseRealizationFo
kandangs: Yup.array() kandangs: Yup.array()
.of( .of(
Yup.object({ Yup.object({
id: Yup.number().required('Kandang wajib dipilih!'), id: Yup.number().optional(),
name: Yup.string().required('Kandang wajib dipilih!'), name: Yup.string().optional(),
}) })
) )
.min(1, 'Kandang wajib dipilih!') .optional(),
.required('Kandang wajib dipilih!'),
supplier: Yup.object({ supplier: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
@@ -73,7 +72,7 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema<ExpenseRealizationFo
realizations: Yup.array() realizations: Yup.array()
.of( .of(
Yup.object({ Yup.object({
kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').required(), kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').optional(),
cost_items: Yup.array() cost_items: Yup.array()
.of( .of(
Yup.object({ Yup.object({
@@ -86,12 +85,12 @@ export const ExpenseRealizationFormSchema: Yup.ObjectSchema<ExpenseRealizationFo
notes: Yup.string(), notes: Yup.string(),
}) })
) )
.min(1, 'Kandang harus memiliki setidaknya 1 biaya!') .min(1, 'Harus memiliki setidaknya 1 biaya!')
.required('Biaya kandang wajib diisi!'), .required('Biaya wajib diisi!'),
}) })
) )
.min(1, 'Biaya kandang wajib diisi!') .min(1, 'Biaya wajib diisi!')
.required('Biaya kandang wajib diisi!'), .required('Biaya wajib diisi!'),
}); });
export const UpdateExpenseRealizationFormSchema = ExpenseRealizationFormSchema; export const UpdateExpenseRealizationFormSchema = ExpenseRealizationFormSchema;
@@ -150,29 +150,10 @@ const ExpenseRealizationForm = ({
formik.setFieldValue('location', val); formik.setFieldValue('location', val);
formik.setFieldValue('kandangs', []); formik.setFieldValue('kandangs', []);
formik.setFieldValue('realizations', []);
};
const kandangsChangeHandler = ( // Auto-create realization item for location (without kandang)
kandangs: { id?: number; name?: string }[] formik.setFieldValue('realizations', [
) => { {
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,
cost_items: [ cost_items: [
{ {
nonstock: undefined, nonstock: undefined,
@@ -181,27 +162,55 @@ const ExpenseRealizationForm = ({
notes: '', notes: '',
}, },
], ],
}); },
}); ]);
};
// prune realizations const kandangsChangeHandler = (
const kandangIds = new Set( kandangs: { id?: number; name?: string }[]
kandangs ) => {
.map((kandang) => kandang.id) formik.setFieldTouched('kandangs', true);
.filter((id): id is number => id !== undefined) formik.setFieldValue('kandangs', kandangs);
);
const deletedRealizationsIdx: number[] = [];
newRealizations.forEach((realization, idx) => { // If no kandangs selected, create realization item for location
const isRealizationValid = kandangIds.has(realization.kandang_id); if (kandangs.length === 0) {
formik.setFieldValue('realizations', [
if (!isRealizationValid) { {
deletedRealizationsIdx.push(idx); cost_items: [
{
nonstock: undefined,
quantity: undefined,
price: undefined,
notes: '',
},
],
},
]);
return;
} }
});
deletedRealizationsIdx.forEach((deletedRealizationIdx) => { // Start with empty array when kandangs are selected
newRealizations.splice(deletedRealizationIdx, 1); 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: '',
},
],
});
}); });
formik.setFieldValue('realizations', newRealizations); formik.setFieldValue('realizations', newRealizations);
@@ -346,7 +355,10 @@ const ExpenseRealizationForm = ({
)} )}
<ExpenseRealizationKandangDetailExpense <ExpenseRealizationKandangDetailExpense
type={type}
formik={formik} formik={formik}
supplierId={formik.values.supplier?.value as number}
location={formik.values.location}
className={{ className={{
wrapper: 'col-span-12', wrapper: 'col-span-12',
}} }}
@@ -18,6 +18,11 @@ import { Nonstock } from '@/types/api/master-data/nonstock';
interface ExpenseRealizationKandangDetailExpenseProps { interface ExpenseRealizationKandangDetailExpenseProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
formik: FormikContextType<ExpenseRealizationFormValues>; formik: FormikContextType<ExpenseRealizationFormValues>;
supplierId?: number;
location?: {
value: number;
label: string;
};
className?: { className?: {
wrapper?: string; wrapper?: string;
}; };
@@ -25,12 +30,18 @@ interface ExpenseRealizationKandangDetailExpenseProps {
const ExpenseRealizationKandangDetailExpense: React.FC< const ExpenseRealizationKandangDetailExpense: React.FC<
ExpenseRealizationKandangDetailExpenseProps ExpenseRealizationKandangDetailExpenseProps
> = ({ type, formik, className }) => { > = ({ type, formik, supplierId, location, className }) => {
const { const {
setInputValue: setNonstockInputValue, setInputValue: setNonstockInputValue,
options: nonstockOptions, options: nonstockOptions,
isLoadingOptions: isLoadingNonstockOptions, isLoadingOptions: isLoadingNonstockOptions,
} = useSelect<Nonstock>(NonstockApi.basePath, 'id', 'name'); } = useSelect<Nonstock>(
NonstockApi.basePath,
'id',
'name',
'search',
supplierId ? { supplier_id: String(supplierId) } : undefined
);
const nonstockChangeHandler = ( const nonstockChangeHandler = (
kandangExpenseIdx: number, kandangExpenseIdx: number,
@@ -82,28 +93,42 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
</div> </div>
<div className='w-full flex flex-col gap-6'> <div className='w-full flex flex-col gap-6'>
{formik.values.realizations.length === 0 && ( {!formik.values.supplier?.value && (
<div> <div>
<p className='text-sm text-gray-400 text-center'> <p className='text-sm text-gray-400 text-center'>
Pilih kandang terlebih dahulu! Pilih supplier terlebih dahulu!
</p> </p>
</div> </div>
)} )}
{formik.values.realizations.map((kandangExpense, kandangExpenseIdx) => { {formik.values.realizations.length === 0 &&
const kandangName = formik.values.kandangs?.find( formik.values.supplier?.value && (
<div>
<p className='text-sm text-gray-400 text-center'>
Belum ada item biaya. Silakan pilih lokasi terlebih dahulu.
</p>
</div>
)}
{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 (kandang) => kandang.id === kandangExpense.kandang_id
); )
: null;
return ( return (
kandangName?.name && ( (kandangName?.name || !kandangExpense.kandang_id) && (
<div <div
key={`kandangExpense-${kandangExpenseIdx}`} key={`kandangExpense-${kandangExpenseIdx}`}
className='w-full flex flex-col gap-4' className='w-full flex flex-col gap-4'
> >
<div> <div>
<h5 className='mb-2 text-lg font-bold text-center'> <h5 className='mb-2 text-lg font-bold text-center'>
Biaya {kandangName?.name} Biaya {kandangName?.name || location?.label || 'Umum'}
</h5> </h5>
<div className='overflow-x-auto'> <div className='overflow-x-auto'>
@@ -215,7 +240,8 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
</div> </div>
) )
); );
})} }
)}
</div> </div>
</Card> </Card>
); );
@@ -22,7 +22,7 @@ type ExpenseFormSchemaType = {
deleted_documents?: number[]; deleted_documents?: number[];
documents?: File[]; documents?: File[];
expense_nonstocks: { expense_nonstocks: {
kandang_id?: number | null; kandang_id?: number;
cost_items: { cost_items: {
nonstock?: { nonstock?: {
value: number; value: number;
@@ -79,10 +79,7 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
expense_nonstocks: Yup.array() expense_nonstocks: Yup.array()
.of( .of(
Yup.object({ Yup.object({
kandang_id: Yup.number() kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').optional(),
.min(1, 'Wajib memilih kandang!')
.nullable()
.optional(),
cost_items: Yup.array() cost_items: Yup.array()
.of( .of(
Yup.object({ Yup.object({
@@ -122,7 +122,7 @@ const ExpenseRequestForm = ({
})), })),
}; };
return expenseNonstock.kandang_id !== null return expenseNonstock.kandang_id
? { ...basePayload, kandang_id: expenseNonstock.kandang_id } ? { ...basePayload, kandang_id: expenseNonstock.kandang_id }
: basePayload; : basePayload;
}), }),
@@ -151,7 +151,7 @@ const ExpenseRequestForm = ({
})), })),
}; };
return expenseNonstock.kandang_id !== null return expenseNonstock.kandang_id
? { ...basePayload, kandang_id: expenseNonstock.kandang_id } ? { ...basePayload, kandang_id: expenseNonstock.kandang_id }
: basePayload; : basePayload;
} }
@@ -199,7 +199,6 @@ const ExpenseRequestForm = ({
// Auto-create expense item for location (without kandang) // Auto-create expense item for location (without kandang)
formik.setFieldValue('expense_nonstocks', [ formik.setFieldValue('expense_nonstocks', [
{ {
kandang_id: null,
cost_items: [ cost_items: [
{ {
nonstock: undefined, nonstock: undefined,
@@ -222,7 +221,6 @@ const ExpenseRequestForm = ({
if (kandangs.length === 0) { if (kandangs.length === 0) {
formik.setFieldValue('expense_nonstocks', [ formik.setFieldValue('expense_nonstocks', [
{ {
kandang_id: null,
cost_items: [ cost_items: [
{ {
nonstock: undefined, nonstock: undefined,