mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 15:25:46 +00:00
refactor(FE-435,436): Allow optional kandang and location expenses
This commit is contained in:
@@ -20,10 +20,10 @@ interface ExpenseKandangsTableProps {
|
|||||||
locationId?: number;
|
locationId?: number;
|
||||||
type: 'add' | 'edit' | 'detail';
|
type: 'add' | 'edit' | 'detail';
|
||||||
selectedKandangs: {
|
selectedKandangs: {
|
||||||
id: number;
|
id?: number;
|
||||||
name: string;
|
name?: string;
|
||||||
}[];
|
}[];
|
||||||
onChange: (kandangs: { id: number; name: string }[]) => void;
|
onChange: (kandangs: { id?: number; name?: string }[]) => void;
|
||||||
className?: {
|
className?: {
|
||||||
wrapper?: string;
|
wrapper?: string;
|
||||||
};
|
};
|
||||||
@@ -67,7 +67,11 @@ const ExpenseKandangsTable = ({
|
|||||||
);
|
);
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(
|
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>[] = [
|
const kandangsColumns: ColumnDef<Kandang>[] = [
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ const ExpenseRealizationForm = ({
|
|||||||
formik.setFieldValue('realizations', []);
|
formik.setFieldValue('realizations', []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
|
const kandangsChangeHandler = (
|
||||||
|
kandangs: { id?: number; name?: string }[]
|
||||||
|
) => {
|
||||||
formik.setFieldTouched('kandangs', true);
|
formik.setFieldTouched('kandangs', true);
|
||||||
formik.setFieldValue('kandangs', kandangs);
|
formik.setFieldValue('kandangs', kandangs);
|
||||||
|
|
||||||
@@ -161,6 +163,8 @@ const ExpenseRealizationForm = ({
|
|||||||
|
|
||||||
// add new realizations
|
// add new realizations
|
||||||
kandangs.forEach((kandangItem) => {
|
kandangs.forEach((kandangItem) => {
|
||||||
|
if (!kandangItem.id) return;
|
||||||
|
|
||||||
const isKandangExistInRealization = newRealizations.find(
|
const isKandangExistInRealization = newRealizations.find(
|
||||||
(realizationItem) => realizationItem.kandang_id === kandangItem.id
|
(realizationItem) => realizationItem.kandang_id === kandangItem.id
|
||||||
);
|
);
|
||||||
@@ -181,7 +185,11 @@ const ExpenseRealizationForm = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// prune realizations
|
// 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[] = [];
|
const deletedRealizationsIdx: number[] = [];
|
||||||
|
|
||||||
newRealizations.forEach((realization, idx) => {
|
newRealizations.forEach((realization, idx) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type ExpenseFormSchemaType = {
|
|||||||
};
|
};
|
||||||
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;
|
||||||
@@ -22,7 +22,7 @@ type ExpenseFormSchemaType = {
|
|||||||
deleted_documents?: number[];
|
deleted_documents?: number[];
|
||||||
documents?: File[];
|
documents?: File[];
|
||||||
expense_nonstocks: {
|
expense_nonstocks: {
|
||||||
kandang_id: number;
|
kandang_id?: number | null;
|
||||||
cost_items: {
|
cost_items: {
|
||||||
nonstock?: {
|
nonstock?: {
|
||||||
value: number;
|
value: number;
|
||||||
@@ -53,12 +53,11 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
|
|||||||
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(),
|
||||||
@@ -80,7 +79,10 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
|
|||||||
expense_nonstocks: Yup.array()
|
expense_nonstocks: 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!')
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
cost_items: Yup.array()
|
cost_items: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const ExpenseRequestForm = ({
|
|||||||
supplier_id: values.supplier?.value as number,
|
supplier_id: values.supplier?.value as number,
|
||||||
documents: values.documents as File[],
|
documents: values.documents as File[],
|
||||||
expense_nonstocks: values.expense_nonstocks.map((expenseNonstock) => ({
|
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) => ({
|
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
||||||
nonstock_id: costItem.nonstock?.value as number,
|
nonstock_id: costItem.nonstock?.value as number,
|
||||||
quantity: parseFloat(String(costItem.quantity)) as number,
|
quantity: parseFloat(String(costItem.quantity)) as number,
|
||||||
@@ -137,7 +137,7 @@ const ExpenseRequestForm = ({
|
|||||||
documents: values.documents as File[],
|
documents: values.documents as File[],
|
||||||
expense_nonstocks: values.expense_nonstocks.map(
|
expense_nonstocks: values.expense_nonstocks.map(
|
||||||
(expenseNonstock) => ({
|
(expenseNonstock) => ({
|
||||||
kandang_id: expenseNonstock.kandang_id,
|
kandang_id: expenseNonstock.kandang_id ?? null,
|
||||||
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
cost_items: expenseNonstock.cost_items.map((costItem) => ({
|
||||||
nonstock_id: costItem.nonstock?.value as number,
|
nonstock_id: costItem.nonstock?.value as number,
|
||||||
quantity: parseFloat(String(costItem.quantity)) as number,
|
quantity: parseFloat(String(costItem.quantity)) as number,
|
||||||
@@ -185,26 +185,11 @@ const ExpenseRequestForm = ({
|
|||||||
formik.setFieldValue('location_id', locationId);
|
formik.setFieldValue('location_id', locationId);
|
||||||
|
|
||||||
formik.setFieldValue('kandangs', []);
|
formik.setFieldValue('kandangs', []);
|
||||||
formik.setFieldValue('expense_nonstocks', []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
|
// Auto-create expense item for location (without kandang)
|
||||||
formik.setFieldTouched('kandangs', true);
|
formik.setFieldValue('expense_nonstocks', [
|
||||||
formik.setFieldValue('kandangs', kandangs);
|
{
|
||||||
|
kandang_id: null,
|
||||||
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,
|
|
||||||
cost_items: [
|
cost_items: [
|
||||||
{
|
{
|
||||||
nonstock: undefined,
|
nonstock: undefined,
|
||||||
@@ -213,25 +198,57 @@ const ExpenseRequestForm = ({
|
|||||||
notes: '',
|
notes: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const kandangsChangeHandler = (
|
||||||
|
kandangs: { id?: number; name?: string }[]
|
||||||
|
) => {
|
||||||
|
formik.setFieldTouched('kandangs', true);
|
||||||
|
formik.setFieldValue('kandangs', kandangs);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// prune expense_nonstocks
|
|
||||||
const kandangIds = new Set(kandangs.map((kandang) => kandang.id));
|
|
||||||
const deletedExpenseNonstocksIdx: number[] = [];
|
|
||||||
|
|
||||||
newExpenseNonstocks.forEach((expenseNonstock, idx) => {
|
|
||||||
const isExpenseNonstockValid = kandangIds.has(expenseNonstock.kandang_id);
|
|
||||||
|
|
||||||
if (!isExpenseNonstockValid) {
|
|
||||||
deletedExpenseNonstocksIdx.push(idx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
deletedExpenseNonstocksIdx.forEach((deletedExpenseNonstockIdx) => {
|
|
||||||
newExpenseNonstocks.splice(deletedExpenseNonstockIdx, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
formik.setFieldValue('expense_nonstocks', newExpenseNonstocks);
|
formik.setFieldValue('expense_nonstocks', newExpenseNonstocks);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -462,6 +479,7 @@ const ExpenseRequestForm = ({
|
|||||||
type={type}
|
type={type}
|
||||||
formik={formik}
|
formik={formik}
|
||||||
supplierId={formik.values.supplier?.value as number}
|
supplierId={formik.values.supplier?.value as number}
|
||||||
|
location={formik.values.location}
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'col-span-12',
|
wrapper: 'col-span-12',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ interface ExpenseRequestKandangDetailExpenseProps {
|
|||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
formik: FormikContextType<ExpenseRequestFormValues>;
|
formik: FormikContextType<ExpenseRequestFormValues>;
|
||||||
supplierId?: number;
|
supplierId?: number;
|
||||||
|
location?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
className?: {
|
className?: {
|
||||||
wrapper?: string;
|
wrapper?: string;
|
||||||
};
|
};
|
||||||
@@ -29,7 +33,7 @@ interface ExpenseRequestKandangDetailExpenseProps {
|
|||||||
|
|
||||||
const ExpenseRequestKandangDetailExpense: React.FC<
|
const ExpenseRequestKandangDetailExpense: React.FC<
|
||||||
ExpenseRequestKandangDetailExpenseProps
|
ExpenseRequestKandangDetailExpenseProps
|
||||||
> = ({ type, formik, supplierId, className }) => {
|
> = ({ type, formik, supplierId, location, className }) => {
|
||||||
const {
|
const {
|
||||||
setInputValue: setNonstockInputValue,
|
setInputValue: setNonstockInputValue,
|
||||||
options: nonstockOptions,
|
options: nonstockOptions,
|
||||||
@@ -120,32 +124,42 @@ const ExpenseRequestKandangDetailExpense: React.FC<
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full flex flex-col gap-6'>
|
<div className='w-full flex flex-col gap-6'>
|
||||||
{(formik.values.expense_nonstocks.length === 0 ||
|
{!formik.values.supplier?.value && (
|
||||||
!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.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>
|
||||||
|
)}
|
||||||
|
|
||||||
{formik.values.expense_nonstocks.length > 0 &&
|
{formik.values.expense_nonstocks.length > 0 &&
|
||||||
formik.values.supplier?.value &&
|
formik.values.supplier?.value &&
|
||||||
formik.values.expense_nonstocks.map(
|
formik.values.expense_nonstocks.map(
|
||||||
(kandangExpense, kandangExpenseIdx) => {
|
(kandangExpense, kandangExpenseIdx) => {
|
||||||
const kandangName = formik.values.kandangs?.find(
|
const kandangName = kandangExpense.kandang_id
|
||||||
(kandang) => kandang.id === kandangExpense.kandang_id
|
? formik.values.kandangs?.find(
|
||||||
);
|
(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'>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -62,7 +62,7 @@ export type CreateExpensePayload = {
|
|||||||
supplier_id: number;
|
supplier_id: number;
|
||||||
documents: File[];
|
documents: File[];
|
||||||
expense_nonstocks: {
|
expense_nonstocks: {
|
||||||
kandang_id: number;
|
kandang_id: number | null;
|
||||||
cost_items: {
|
cost_items: {
|
||||||
nonstock_id: number;
|
nonstock_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -79,7 +79,7 @@ export type UpdateExpensePayload = {
|
|||||||
supplier_id: number;
|
supplier_id: number;
|
||||||
documents: File[];
|
documents: File[];
|
||||||
expense_nonstocks: {
|
expense_nonstocks: {
|
||||||
kandang_id: number;
|
kandang_id: number | null;
|
||||||
cost_items: {
|
cost_items: {
|
||||||
nonstock_id: number;
|
nonstock_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user