refactor(FE-435,436): Allow optional kandang and location expenses

This commit is contained in:
rstubryan
2025-12-30 18:53:46 +07:00
parent 8f4f3d93b8
commit d2e88c2061
6 changed files with 107 additions and 61 deletions
@@ -20,10 +20,10 @@ interface ExpenseKandangsTableProps {
locationId?: number;
type: 'add' | 'edit' | 'detail';
selectedKandangs: {
id: number;
name: string;
id?: number;
name?: string;
}[];
onChange: (kandangs: { id: number; name: string }[]) => void;
onChange: (kandangs: { id?: number; name?: string }[]) => void;
className?: {
wrapper?: string;
};
@@ -67,7 +67,11 @@ const ExpenseKandangsTable = ({
);
const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(
convertRowSelectionArrToObj(selectedKandangs.map((item) => item.id))
convertRowSelectionArrToObj(
selectedKandangs
.map((item) => item.id)
.filter((id): id is number => id !== undefined)
)
);
const kandangsColumns: ColumnDef<Kandang>[] = [
@@ -153,7 +153,9 @@ const ExpenseRealizationForm = ({
formik.setFieldValue('realizations', []);
};
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
const kandangsChangeHandler = (
kandangs: { id?: number; name?: string }[]
) => {
formik.setFieldTouched('kandangs', true);
formik.setFieldValue('kandangs', kandangs);
@@ -161,6 +163,8 @@ const ExpenseRealizationForm = ({
// add new realizations
kandangs.forEach((kandangItem) => {
if (!kandangItem.id) return;
const isKandangExistInRealization = newRealizations.find(
(realizationItem) => realizationItem.kandang_id === kandangItem.id
);
@@ -181,7 +185,11 @@ const ExpenseRealizationForm = ({
});
// prune realizations
const kandangIds = new Set(kandangs.map((kandang) => kandang.id));
const kandangIds = new Set(
kandangs
.map((kandang) => kandang.id)
.filter((id): id is number => id !== undefined)
);
const deletedRealizationsIdx: number[] = [];
newRealizations.forEach((realization, idx) => {
@@ -13,7 +13,7 @@ type ExpenseFormSchemaType = {
};
location_id: number;
transaction_date?: string;
kandangs?: { id: number; name: string }[];
kandangs?: { id?: number; name?: string }[];
supplier?: {
value: number;
label: string;
@@ -22,7 +22,7 @@ type ExpenseFormSchemaType = {
deleted_documents?: number[];
documents?: File[];
expense_nonstocks: {
kandang_id: number;
kandang_id?: number | null;
cost_items: {
nonstock?: {
value: number;
@@ -53,12 +53,11 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
kandangs: Yup.array()
.of(
Yup.object({
id: Yup.number().required('Kandang wajib dipilih!'),
name: Yup.string().required('Kandang wajib dipilih!'),
id: Yup.number().optional(),
name: Yup.string().optional(),
})
)
.min(1, 'Kandang wajib dipilih!')
.required('Kandang wajib dipilih!'),
.optional(),
supplier: Yup.object({
value: Yup.number().min(1).required(),
@@ -80,7 +79,10 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
expense_nonstocks: Yup.array()
.of(
Yup.object({
kandang_id: Yup.number().min(1, 'Wajib memilih kandang!').required(),
kandang_id: Yup.number()
.min(1, 'Wajib memilih kandang!')
.nullable()
.optional(),
cost_items: Yup.array()
.of(
Yup.object({
@@ -113,7 +113,7 @@ const ExpenseRequestForm = ({
supplier_id: values.supplier?.value as number,
documents: values.documents as File[],
expense_nonstocks: values.expense_nonstocks.map((expenseNonstock) => ({
kandang_id: expenseNonstock.kandang_id,
kandang_id: expenseNonstock.kandang_id ?? null,
cost_items: expenseNonstock.cost_items.map((costItem) => ({
nonstock_id: costItem.nonstock?.value as number,
quantity: parseFloat(String(costItem.quantity)) as number,
@@ -137,7 +137,7 @@ const ExpenseRequestForm = ({
documents: values.documents as File[],
expense_nonstocks: values.expense_nonstocks.map(
(expenseNonstock) => ({
kandang_id: expenseNonstock.kandang_id,
kandang_id: expenseNonstock.kandang_id ?? null,
cost_items: expenseNonstock.cost_items.map((costItem) => ({
nonstock_id: costItem.nonstock?.value as number,
quantity: parseFloat(String(costItem.quantity)) as number,
@@ -185,26 +185,11 @@ const ExpenseRequestForm = ({
formik.setFieldValue('location_id', locationId);
formik.setFieldValue('kandangs', []);
formik.setFieldValue('expense_nonstocks', []);
};
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
formik.setFieldTouched('kandangs', true);
formik.setFieldValue('kandangs', kandangs);
const newExpenseNonstocks = [...(formik.values.expense_nonstocks ?? [])];
// add new expense_nonstocks
kandangs.forEach((kandangItem) => {
const isKandangExistInExpenseNonstocks = newExpenseNonstocks.find(
(expenseNonstockItem) =>
expenseNonstockItem.kandang_id === kandangItem.id
);
if (isKandangExistInExpenseNonstocks) return;
newExpenseNonstocks.push({
kandang_id: kandangItem.id,
// Auto-create expense item for location (without kandang)
formik.setFieldValue('expense_nonstocks', [
{
kandang_id: null,
cost_items: [
{
nonstock: undefined,
@@ -213,23 +198,55 @@ const ExpenseRequestForm = ({
notes: '',
},
],
});
});
},
]);
};
// prune expense_nonstocks
const kandangIds = new Set(kandangs.map((kandang) => kandang.id));
const deletedExpenseNonstocksIdx: number[] = [];
const kandangsChangeHandler = (
kandangs: { id?: number; name?: string }[]
) => {
formik.setFieldTouched('kandangs', true);
formik.setFieldValue('kandangs', kandangs);
newExpenseNonstocks.forEach((expenseNonstock, idx) => {
const isExpenseNonstockValid = kandangIds.has(expenseNonstock.kandang_id);
if (!isExpenseNonstockValid) {
deletedExpenseNonstocksIdx.push(idx);
// If no kandangs selected, create expense item for location
if (kandangs.length === 0) {
formik.setFieldValue('expense_nonstocks', [
{
kandang_id: null,
cost_items: [
{
nonstock: undefined,
quantity: undefined,
price: undefined,
notes: '',
},
],
},
]);
return;
}
});
deletedExpenseNonstocksIdx.forEach((deletedExpenseNonstockIdx) => {
newExpenseNonstocks.splice(deletedExpenseNonstockIdx, 1);
const newExpenseNonstocks: typeof formik.values.expense_nonstocks = [];
kandangs.forEach((kandangItem) => {
if (!kandangItem.id) return;
const existingExpenseNonstock = formik.values.expense_nonstocks?.find(
(expenseNonstockItem) =>
expenseNonstockItem.kandang_id === kandangItem.id
);
newExpenseNonstocks.push({
kandang_id: kandangItem.id,
cost_items: existingExpenseNonstock?.cost_items || [
{
nonstock: undefined,
quantity: undefined,
price: undefined,
notes: '',
},
],
});
});
formik.setFieldValue('expense_nonstocks', newExpenseNonstocks);
@@ -462,6 +479,7 @@ const ExpenseRequestForm = ({
type={type}
formik={formik}
supplierId={formik.values.supplier?.value as number}
location={formik.values.location}
className={{
wrapper: 'col-span-12',
}}
@@ -22,6 +22,10 @@ interface ExpenseRequestKandangDetailExpenseProps {
type?: 'add' | 'edit' | 'detail';
formik: FormikContextType<ExpenseRequestFormValues>;
supplierId?: number;
location?: {
value: number;
label: string;
};
className?: {
wrapper?: string;
};
@@ -29,7 +33,7 @@ interface ExpenseRequestKandangDetailExpenseProps {
const ExpenseRequestKandangDetailExpense: React.FC<
ExpenseRequestKandangDetailExpenseProps
> = ({ type, formik, supplierId, className }) => {
> = ({ type, formik, supplierId, location, className }) => {
const {
setInputValue: setNonstockInputValue,
options: nonstockOptions,
@@ -120,11 +124,19 @@ const ExpenseRequestKandangDetailExpense: React.FC<
</div>
<div className='w-full flex flex-col gap-6'>
{(formik.values.expense_nonstocks.length === 0 ||
!formik.values.supplier?.value) && (
{!formik.values.supplier?.value && (
<div>
<p className='text-sm text-gray-400 text-center'>
Pilih kandang terlebih dahulu!
Pilih supplier terlebih dahulu!
</p>
</div>
)}
{formik.values.expense_nonstocks.length === 0 &&
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>
)}
@@ -133,19 +145,21 @@ const ExpenseRequestKandangDetailExpense: React.FC<
formik.values.supplier?.value &&
formik.values.expense_nonstocks.map(
(kandangExpense, kandangExpenseIdx) => {
const kandangName = formik.values.kandangs?.find(
const kandangName = kandangExpense.kandang_id
? formik.values.kandangs?.find(
(kandang) => kandang.id === kandangExpense.kandang_id
);
)
: null;
return (
kandangName?.name && (
(kandangName?.name || !kandangExpense.kandang_id) && (
<div
key={`kandangExpense-${kandangExpenseIdx}`}
className='w-full flex flex-col gap-4'
>
<div>
<h5 className='mb-2 text-lg font-bold text-center'>
Biaya {kandangName?.name}
Biaya {kandangName?.name || location?.label || 'Umum'}
</h5>
<div className='overflow-x-auto'>
+2 -2
View File
@@ -62,7 +62,7 @@ export type CreateExpensePayload = {
supplier_id: number;
documents: File[];
expense_nonstocks: {
kandang_id: number;
kandang_id: number | null;
cost_items: {
nonstock_id: number;
quantity: number;
@@ -79,7 +79,7 @@ export type UpdateExpensePayload = {
supplier_id: number;
documents: File[];
expense_nonstocks: {
kandang_id: number;
kandang_id: number | null;
cost_items: {
nonstock_id: number;
quantity: number;