From a82c5e55934a5b4aea4fa2a8a8ecc3152fe4d296 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 16:16:34 +0700 Subject: [PATCH 01/52] refactor(FE): Rename price to unit_price in customer payments --- .../report/finance/export/CustomerPaymentExportPDF.tsx | 4 ++-- .../report/finance/export/CustomerPaymentExportXLSX.tsx | 6 +++--- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 8 ++++---- src/types/api/report/customer-payment.d.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 9b1fd640..aa04b4f0 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -300,7 +300,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Rata-Rata - Harga Awal + Harga/Unit Harga Akhir @@ -378,7 +378,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatNumber(item.average_weight)} - {formatCurrency(item.price)} + {formatCurrency(item.unit_price)} {formatCurrency(item.final_price)} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index 3fb21488..830df633 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -38,7 +38,7 @@ export const generateCustomerPaymentExcel = ( 'Ekor/Qty': formatNumber(item.qty || 0), 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), - 'Harga Awal': formatCurrency(item.price || 0), + 'Harga/Unit': formatCurrency(item.unit_price || 0), 'Harga Akhir': formatCurrency(item.final_price || 0), Total: formatCurrency(item.total_price || 0), Pembayaran: formatCurrency(item.payment_amount || 0), @@ -62,7 +62,7 @@ export const generateCustomerPaymentExcel = ( 'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0), 'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0), AVG: '', - 'Harga Awal': '', + 'Harga/Unit': '', 'Harga Akhir': formatCurrency( customerReport.summary.total_final_amount || 0 ), @@ -89,7 +89,7 @@ export const generateCustomerPaymentExcel = ( { wch: 10 }, // Ekor/Qty { wch: 12 }, // Berat { wch: 10 }, // AVG - { wch: 15 }, // Harga Awal + { wch: 15 }, // Harga/Unit { wch: 15 }, // Harga Akhir { wch: 15 }, // Total { wch: 15 }, // Pembayaran diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index ef748b5f..0e3afe40 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -405,12 +405,12 @@ const CustomerPaymentTab = () => { ), }, { - id: 'price', - header: 'Harga Awal', - accessorKey: 'price', + id: 'unit_price', + header: 'Harga/Unit', + accessorKey: 'unit_price', enableSorting: false, cell: (props) => { - const value = props.row.original.price; + const value = props.row.original.unit_price; return
{formatCurrency(value)}
; }, footer: () => ( diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index 9169c99b..90834cdc 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -11,7 +11,7 @@ export type CustomerPaymentRow = { qty: number; weight: number; average_weight: number; - price: number; + unit_price: number; final_price: number; total_price: number; payment_amount: number; From 4d319ca9c813f424f1be53bcb175f84055be84bc Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 20:25:23 +0700 Subject: [PATCH 02/52] refactor(FE): Comment out age column in SalesReportTable --- .../pages/closing/sale/SalesReportTable.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index fe8d46a5..6c12347e 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -82,12 +82,12 @@ const SalesReportTable = ({
Total Penjualan
), }, - { - id: 'age', - accessorKey: 'age', - header: 'Umur', - cell: (props) => props.getValue() || '-', - }, + // { + // id: 'age', + // accessorKey: 'age', + // header: 'Umur', + // cell: (props) => props.getValue() || '-', + // }, { id: 'do_number', accessorKey: 'do_number', From 6377557ef0929c2eec018cb439438cd78c2dd71a Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 20:33:13 +0700 Subject: [PATCH 03/52] refactor(FE): Reset products and deliveries on warehouse change --- .../inventory/movement/form/MovementForm.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 40e08c5d..f866192a 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -993,6 +993,35 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) { formik.setFieldError('destination_warehouse_id', undefined); } + + if ( + newSourceWarehouseId && + newSourceWarehouseId !== formik.values.source_warehouse_id + ) { + formik.setFieldValue('products', [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ]); + formik.setFieldTouched('products', false); + + const updatedDeliveries = formik.values.deliveries.map( + (delivery) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + }) + ); + formik.setFieldValue('deliveries', updatedDeliveries); + formik.setFieldTouched('deliveries', false); + } }} options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} @@ -1252,6 +1281,23 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { `products.${idx}.product_id`, (val as ProductWarehouseOptionType)?.value ); + + const updatedDeliveries = + formik.values.deliveries.map((delivery) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + })); + formik.setFieldValue( + 'deliveries', + updatedDeliveries + ); + formik.setFieldTouched('deliveries', false); }} options={productWarehouseOptions} onInputChange={setProductWarehouseSelectInputValue} From 4391fe1de7d7424ef7b2350075cce72f923f1d1b Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 20:37:43 +0700 Subject: [PATCH 04/52] refactor(FE): Capitalize payment status text --- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 0e3afe40..04c67151 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -106,7 +106,11 @@ const CustomerPaymentTab = () => { }; const getPaymentStatusText = (notes: string) => { - return notes; + return notes + .toLowerCase() + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); }; // ===== FILTER HANDLERS ===== @@ -510,7 +514,7 @@ const CustomerPaymentTab = () => { status: getPaymentStatusIndicatorColor(value), }} > - {getPaymentStatusText(value)} + {getPaymentStatusText(value)} ); }, From 0aa96b9c46ad9c25c180121c4ccbba0a253c5b3a Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 20:53:39 +0700 Subject: [PATCH 05/52] refactor(FE): Remove max-width constraints, use two-column grid --- .../pages/expense/ExpenseDetail.tsx | 4 +- .../expense/ExpenseRealizationContent.tsx | 242 +++++++++--------- .../pages/expense/ExpenseRequestContent.tsx | 8 +- .../expense/form/ExpenseRealizationForm.tsx | 2 +- .../pages/expense/form/ExpenseRequestForm.tsx | 2 +- 5 files changed, 136 insertions(+), 122 deletions(-) diff --git a/src/components/pages/expense/ExpenseDetail.tsx b/src/components/pages/expense/ExpenseDetail.tsx index 9c84ed4d..1f43eae1 100644 --- a/src/components/pages/expense/ExpenseDetail.tsx +++ b/src/components/pages/expense/ExpenseDetail.tsx @@ -43,7 +43,7 @@ const ExpenseDetail: React.FC = ({ initialValues }) => { return ( <> -
+
diff --git a/src/components/pages/expense/ExpenseRealizationContent.tsx b/src/components/pages/expense/ExpenseRealizationContent.tsx index ea4a0e8d..abe00912 100644 --- a/src/components/pages/expense/ExpenseRealizationContent.tsx +++ b/src/components/pages/expense/ExpenseRealizationContent.tsx @@ -68,7 +68,7 @@ const ExpenseRealizationContent = ({ return (
-
+
-
+
@@ -179,7 +179,7 @@ const ExpenseRealizationContent = ({
-
+
@@ -216,127 +216,141 @@ const ExpenseRealizationContent = ({
-
-

- Rincian Pengajuan Biaya Operasional -

+
+
+

+ Rincian Pengajuan Biaya Operasional +

-
- {initialValues?.kandangs.map((kandangExpense, kandangExpenseIdx) => { - let expenseGrandTotal = 0; +
+ {initialValues?.kandangs.map( + (kandangExpense, kandangExpenseIdx) => { + let expenseGrandTotal = 0; - kandangExpense.pengajuans?.forEach( - (item) => (expenseGrandTotal += item.qty * item.price) - ); + kandangExpense.pengajuans?.forEach( + (item) => (expenseGrandTotal += item.qty * item.price) + ); - return ( -
- - - - - - - - - - - - - - {kandangExpense.pengajuans?.map( - (pengajuanItem, pengajuanIdx) => ( - - - - - + return ( +
+
- Biaya {kandangExpense.name} -
NonstockTotal KuantitasTotal BiayaCatatan
{pengajuanItem.nonstock.name}{pengajuanItem.qty}{formatCurrency(pengajuanItem.price)}{pengajuanItem.note ?? '-'}
+ + + - ) - )} - - - - - - - -
+ Biaya {kandangExpense.name} +
- Total Biaya Keseluruhan: - {formatCurrency(expenseGrandTotal)}
-
- ); - })} + + Nonstock + Total Kuantitas + Total Biaya + Catatan + + + + {kandangExpense.pengajuans?.map( + (pengajuanItem, pengajuanIdx) => ( + + {pengajuanItem.nonstock.name} + {pengajuanItem.qty} + {formatCurrency(pengajuanItem.price)} + + {pengajuanItem.note ?? '-'} + + + ) + )} + + + + + Total Biaya Keseluruhan: + + + {formatCurrency(expenseGrandTotal)} + + + + +
+ ); + } + )} +
-
-
-

- Rincian Realisasi Biaya Operasional -

+
+

+ Rincian Realisasi Biaya Operasional +

-
- {initialValues?.kandangs.map((kandangExpense, kandangExpenseIdx) => { - let expenseGrandTotal = 0; +
+ {initialValues?.kandangs.map( + (kandangExpense, kandangExpenseIdx) => { + let expenseGrandTotal = 0; - kandangExpense.realisasi?.forEach( - (item) => (expenseGrandTotal += item.qty * item.price) - ); + kandangExpense.realisasi?.forEach( + (item) => (expenseGrandTotal += item.qty * item.price) + ); - return ( -
- - - - - - - - - - - - - - {kandangExpense.realisasi?.map( - (realisasiItem, realisasiIdx) => ( - - - - - + return ( +
+
- Biaya {kandangExpense.name} -
NonstockTotal KuantitasTotal BiayaCatatan
{realisasiItem.nonstock.name}{realisasiItem.qty}{formatCurrency(realisasiItem.price)}{realisasiItem.note ?? '-'}
+ + + - ) - )} - - - - - - - -
+ Biaya {kandangExpense.name} +
- Total Biaya Keseluruhan: - {formatCurrency(expenseGrandTotal)}
-
- ); - })} + + Nonstock + Total Kuantitas + Total Biaya + Catatan + + + + {kandangExpense.realisasi?.map( + (realisasiItem, realisasiIdx) => ( + + {realisasiItem.nonstock.name} + {realisasiItem.qty} + {formatCurrency(realisasiItem.price)} + + {realisasiItem.note ?? '-'} + + + ) + )} + + + + + Total Biaya Keseluruhan: + + + {formatCurrency(expenseGrandTotal)} + + + + +
+ ); + } + )} +
diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index a1ad4643..ed39ea32 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -273,7 +273,7 @@ const ExpenseRequestContent = ({ <>
{initialValues && !isLoadingApprovalHistory && approvalHistory && ( -
+
)} @@ -281,7 +281,7 @@ const ExpenseRequestContent = ({
{/* TODO: apply RBAC */} -
+
{isCurrentApprovalOnHeadArea && (
-
+
@@ -608,7 +608,7 @@ const ExpenseRequestContent = ({
-
+

Rincian Pengajuan Biaya Operasional

diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.tsx b/src/components/pages/expense/form/ExpenseRealizationForm.tsx index ed5aea3e..9064881b 100644 --- a/src/components/pages/expense/form/ExpenseRealizationForm.tsx +++ b/src/components/pages/expense/form/ExpenseRealizationForm.tsx @@ -249,7 +249,7 @@ const ExpenseRealizationForm = ({ }, [formikSetValues, getExpenseRealizationFormInitialValues, initialValues]); return ( -
+
- } - className='w-full!' - titleClassName='w-full p-0!' - > - - data={isResponseSuccess(kandangs) ? kandangs?.data : []} - columns={kandangsColumns} - pageSize={tableFilterState.pageSize} - page={isResponseSuccess(kandangs) ? kandangs?.meta?.page : 0} - totalItems={ - isResponseSuccess(kandangs) ? kandangs?.meta?.total_results : 0 - } - onPageChange={setPage} - isLoading={isLoading} - sorting={sorting} - setSorting={setSorting} - rowSelection={rowSelection} - setRowSelection={setRowSelection} + <> + {selectedKandangs.length > 0 && selectedKandangs.some((k) => k.id) && ( + - - + > + +
+ {formType === 'realization' + ? 'Kandang yang Direalisasikan' + : 'Pilih Kandang'} +
+ + +
+ } + className='w-full!' + titleClassName='w-full p-0!' + > + + data={isResponseSuccess(kandangs) ? kandangs?.data : []} + columns={kandangsColumns} + pageSize={tableFilterState.pageSize} + page={isResponseSuccess(kandangs) ? kandangs?.meta?.page : 0} + totalItems={ + isResponseSuccess(kandangs) ? kandangs?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn({ + 'mb-20': + isResponseSuccess(kandangs) && kandangs?.data?.length === 0, + }), + tableWrapperClassName: 'overflow-x-auto min-h-full!', + tableClassName: 'font-inter w-full table-auto min-h-full!', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-6 py-3 text-xs font-semibold text-gray-500 first:flex first:flex-row first:justify-start', + bodyRowClassName: 'border-b border-b-gray-200', + bodyColumnClassName: + 'px-6 py-3 first:flex first:flex-row first:justify-start', + paginationClassName: cn({ + hidden: + isResponseSuccess(kandangs) && + kandangs?.meta?.total_pages === 1, + }), + }} + /> + + + )} + ); }; diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts b/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts index 1f3682ea..037f5442 100644 --- a/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts @@ -130,7 +130,7 @@ export const getExpenseRealizationFormInitialValues = ( ? formatDate(initialValues?.realization_date, 'YYYY-MM-DD') : undefined, kandangs: initialValues?.kandangs.map((kandang) => ({ - id: kandang.kandang_id, + id: kandang.id, name: kandang.name, })), supplier: initialValues?.supplier diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.tsx b/src/components/pages/expense/form/ExpenseRealizationForm.tsx index 9064881b..6117c920 100644 --- a/src/components/pages/expense/form/ExpenseRealizationForm.tsx +++ b/src/components/pages/expense/form/ExpenseRealizationForm.tsx @@ -297,6 +297,7 @@ const ExpenseRealizationForm = ({ Date: Mon, 19 Jan 2026 09:19:47 +0700 Subject: [PATCH 12/52] refactor(FE): Rename customer_id to customer_ids in API and UI --- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 8 ++++---- src/services/api/report/finance-report.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 04c67151..7a739dc7 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -163,7 +163,7 @@ const CustomerPaymentTab = () => { isSubmitted ? () => { const params = { - customer_id: + customer_ids: filterCustomer.length > 0 ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, @@ -184,7 +184,7 @@ const CustomerPaymentTab = () => { : null, ([, params]) => FinanceApi.getCustomerPaymentReport( - params.customer_id, + params.customer_ids, undefined, // TODO: Change to params.sales_id when BE is ready undefined, // TODO: Change to params.filter_by when BE is ready params.start_date, @@ -207,7 +207,7 @@ const CustomerPaymentTab = () => { CustomerPaymentReport[] | null > => { const params = { - customer_id: + customer_ids: filterCustomer.length > 0 ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, @@ -223,7 +223,7 @@ const CustomerPaymentTab = () => { }; const response = await FinanceApi.getCustomerPaymentReport( - params.customer_id, + params.customer_ids, undefined, // TODO: Change to params.sales_id when BE is ready undefined, // TODO: Change to params.filter_by when BE is ready params.start_date, diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 81f23481..1102f99c 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -12,7 +12,7 @@ export class FinanceApiService extends BaseApiService< } async getCustomerPaymentReport( - customer_id?: string, + customer_ids?: string, // TODO: Uncomment when BE is ready // sales_id?: string, // filter_by?: 'do_date', @@ -28,7 +28,7 @@ export class FinanceApiService extends BaseApiService< { method: 'GET', params: { - customer_id: customer_id, + customer_ids: customer_ids, // TODO: Uncomment when BE is ready // sales_id: sales_id, // filter_by: filter_by, From aaaa126c4296f308d0a3180c384b04bd83555fba Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 09:32:59 +0700 Subject: [PATCH 13/52] refactor(FE): Display zero and negative aging days --- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 7a739dc7..b5e8e438 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -340,7 +340,9 @@ const CustomerPaymentTab = () => { const value = props.row.original.aging_day; return (
- {value && value > 0 ? `${formatNumber(value)} hari` : '-'} + {value !== null && value !== undefined + ? `${formatNumber(value)} hari` + : '-'}
); }, From 6def4e0fcd4c743e2c93c7c69943e3af1f392d5e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 10:16:46 +0700 Subject: [PATCH 14/52] refactor(FE): Extract DeliveryDocumentSchema for reuse --- .../movement/form/MovementForm.schema.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.schema.ts b/src/components/pages/inventory/movement/form/MovementForm.schema.ts index 048b1bd2..1a29fa94 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.schema.ts +++ b/src/components/pages/inventory/movement/form/MovementForm.schema.ts @@ -110,6 +110,14 @@ const DeliveryProductObjectSchema = Yup.object({ .typeError('Qty harus berupa angka!'), }); +const DeliveryDocumentSchema = Yup.mixed() + .nullable() + .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value): boolean => { + if (!value) return true; + if (value instanceof File) return value.size <= 5 * 1024 * 1024; + return true; + }); + const DeliveryObjectSchema: Yup.ObjectSchema = Yup.object({ delivery_cost: Yup.number() .transform((value) => (isNaN(value) || value === 0 ? undefined : value)) @@ -135,13 +143,7 @@ const DeliveryObjectSchema: Yup.ObjectSchema = Yup.object({ }), document_path: Yup.string().nullable().optional(), document_index: Yup.number().optional(), - document: Yup.mixed() - .nullable() - .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { - if (!value) return true; - if (value instanceof File) return value.size <= 5 * 1024 * 1024; - return true; - }), + document: DeliveryDocumentSchema, driver_name: Yup.string().required('Nama sopir wajib diisi!'), vehicle_plate: Yup.string().required('Plat nomor wajib diisi!'), supplier: Yup.object({ From dc2c2228a8a7931faf9fc57a0dcb243361d860ce Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 10:28:08 +0700 Subject: [PATCH 15/52] refactor(FE): Extract and memoize form event handlers --- .../inventory/movement/form/MovementForm.tsx | 583 +++++++++--------- 1 file changed, 285 insertions(+), 298 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 6bc41b2c..8137260f 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -349,13 +349,111 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }; }; - const handleTransferDateChange = (e: React.ChangeEvent) => { - formik.setFieldValue('transfer_date', e.target.value); - }; - // ===== EVENT HANDLERS ===== - // Product Handlers - const addProduct = () => { + const handleTransferDateChange = useCallback( + (e: React.ChangeEvent) => { + formik.setFieldValue('transfer_date', e.target.value); + }, + [] + ); + + const handleSourceWarehouseChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const newSourceWarehouseId = (val as WarehouseOptionType)?.value; + + if (newSourceWarehouseId) { + if (newSourceWarehouseId === formik.values.destination_warehouse_id) { + const destinationWarehouseName = + (formik.values.destination_warehouse as WarehouseOptionType) + ?.label || 'gudang tujuan'; + + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang asal tidak boleh sama dengan ${destinationWarehouseName}.` + ); + return; + } + } + + formik.setFieldTouched('source_warehouse', true); + formik.setFieldValue('source_warehouse', val); + formik.setFieldTouched('source_warehouse_id', true); + formik.setFieldValue('source_warehouse_id', newSourceWarehouseId); + + if ( + formik.errors.destination_warehouse_id === + 'Gudang tujuan tidak boleh sama dengan gudang asal!' + ) { + formik.setFieldError('destination_warehouse_id', undefined); + } + + if ( + newSourceWarehouseId && + newSourceWarehouseId !== formik.values.source_warehouse_id + ) { + formik.setFieldValue('products', [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ]); + formik.setFieldTouched('products', false); + + const updatedDeliveries = formik.values.deliveries.map( + (delivery: DeliverySchema) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + }) + ); + formik.setFieldValue('deliveries', updatedDeliveries); + formik.setFieldTouched('deliveries', false); + } + }, + [] + ); + + const handleDestinationWarehouseChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const newDestinationWarehouseId = (val as WarehouseOptionType)?.value; + + if (newDestinationWarehouseId) { + if (newDestinationWarehouseId === formik.values.source_warehouse_id) { + const sourceWarehouseName = + (formik.values.source_warehouse as WarehouseOptionType)?.label || + 'gudang asal'; + + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` + ); + return; + } + } + + formik.setFieldTouched('destination_warehouse', true); + formik.setFieldValue('destination_warehouse', val); + formik.setFieldTouched('destination_warehouse_id', true); + formik.setFieldValue( + 'destination_warehouse_id', + newDestinationWarehouseId + ); + + if ( + formik.errors.destination_warehouse_id === + 'Gudang tujuan tidak boleh sama dengan gudang asal!' + ) { + formik.setFieldError('destination_warehouse_id', undefined); + } + }, + [] + ); + + const addProduct = useCallback(() => { const newProducts = [ ...(formik.values.products || []), { @@ -365,22 +463,19 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, ]; formik.setFieldValue('products', newProducts); - }; + }, []); - const removeProduct = useCallback( - (i: number) => { - const updatedProducts = - formik.values.products?.reduce((acc: ProductSchema[], item, index) => { - if (index !== i) { - acc.push(item); - } - return acc; - }, []) ?? []; + const removeProduct = useCallback((i: number) => { + const updatedProducts = + formik.values.products?.reduce((acc: ProductSchema[], item, index) => { + if (index !== i) { + acc.push(item); + } + return acc; + }, []) ?? []; - formik.setFieldValue('products', updatedProducts); - }, - [formik] - ); + formik.setFieldValue('products', updatedProducts); + }, []); const bulkRemoveProduct = useCallback(() => { const updatedProducts = @@ -389,10 +484,60 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) ?? []; formik.setFieldValue('products', updatedProducts); setSelectedProducts([]); - }, [formik, selectedProducts]); + }, [formik, selectedProducts, setSelectedProducts]); - // Delivery Handlers - const addDelivery = () => { + const handleProductChange = useCallback( + (idx: number, val: OptionType | OptionType[] | null) => { + formik.setFieldTouched(`products.${idx}.product`, true); + formik.setFieldValue(`products.${idx}.product`, val); + formik.setFieldTouched(`products.${idx}.product_id`, true); + formik.setFieldValue( + `products.${idx}.product_id`, + (val as ProductWarehouseOptionType)?.value + ); + + const updatedDeliveries = formik.values.deliveries.map( + (delivery: DeliverySchema) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + }) + ); + formik.setFieldValue('deliveries', updatedDeliveries); + formik.setFieldTouched('deliveries', false); + }, + [] + ); + + const handleProductSelectAllChange = useCallback( + (e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedProducts(formik.values.products?.map((_, idx) => idx) ?? []); + } else { + setSelectedProducts([]); + } + }, + [formik.values.products, setSelectedProducts] + ); + + const handleProductCheckboxChange = useCallback( + (e: React.ChangeEvent) => { + const idx = Number(e.target.name.replace('product-', '')); + if (e.target.checked) { + setSelectedProducts((prev) => [...prev, idx]); + } else { + setSelectedProducts((prev) => prev.filter((i) => i !== idx)); + } + }, + [setSelectedProducts] + ); + + const addDelivery = useCallback(() => { formik.setFieldValue('deliveries', [ ...(formik.values.deliveries || []), { @@ -412,25 +557,19 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ], }, ]); - }; + }, []); - const removeDelivery = useCallback( - (i: number) => { - const updatedDeliveries = - formik.values.deliveries?.reduce( - (acc: DeliverySchema[], item, index) => { - if (index !== i) { - acc.push(item); - } - return acc; - }, - [] - ) ?? []; + const removeDelivery = useCallback((i: number) => { + const updatedDeliveries = + formik.values.deliveries?.reduce((acc: DeliverySchema[], item, index) => { + if (index !== i) { + acc.push(item); + } + return acc; + }, []) ?? []; - formik.setFieldValue('deliveries', updatedDeliveries); - }, - [formik] - ); + formik.setFieldValue('deliveries', updatedDeliveries); + }, []); const bulkRemoveDelivery = useCallback(() => { const updatedDeliveries = @@ -439,33 +578,101 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) ?? []; formik.setFieldValue('deliveries', updatedDeliveries); setSelectedDeliveries([]); - }, [formik, selectedDeliveries]); + }, [formik, selectedDeliveries, setSelectedDeliveries]); - // Cost Calculation Handlers - const handleDeliveryCostChange = useCallback( - (idx: number, value: number) => { - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value); - - const delivery = formik.values.deliveries?.[idx]; - if (delivery) { - const productQty = delivery.products.reduce( - (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), - 0 + const handleDeliverySelectAllChange = useCallback( + (e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedDeliveries( + formik.values.deliveries?.map((_, idx) => idx) ?? [] ); - if (productQty > 0 && value > 0) { - const perItem = value / productQty; - formik.setFieldValue( - `deliveries.${idx}.delivery_cost_per_item`, - perItem - ); - } else if (value === 0) { - formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0); - } + } else { + setSelectedDeliveries([]); } }, - [formik] + [formik.values.deliveries, setSelectedDeliveries] ); + const handleDeliveryCheckboxChange = useCallback( + (e: React.ChangeEvent) => { + const idx = Number(e.target.name.replace('delivery-', '')); + if (e.target.checked) { + setSelectedDeliveries((prev) => [...prev, idx]); + } else { + setSelectedDeliveries((prev) => prev.filter((i) => i !== idx)); + } + }, + [setSelectedDeliveries] + ); + + const handleDeliveryProductChange = useCallback( + (deliveryIdx: number, val: OptionType | OptionType[] | null) => { + formik.setFieldTouched( + `deliveries.${deliveryIdx}.products.0.product`, + true + ); + formik.setFieldValue(`deliveries.${deliveryIdx}.products.0.product`, val); + formik.setFieldTouched( + `deliveries.${deliveryIdx}.products.0.product_id`, + true + ); + formik.setFieldValue( + `deliveries.${deliveryIdx}.products.0.product_id`, + (val as OptionType)?.value + ); + }, + [] + ); + + const handleDeliverySupplierChange = useCallback( + (deliveryIdx: number, val: OptionType | OptionType[] | null) => { + formik.setFieldTouched(`deliveries.${deliveryIdx}.supplier`, true); + formik.setFieldValue(`deliveries.${deliveryIdx}.supplier`, val); + formik.setFieldTouched(`deliveries.${deliveryIdx}.supplier_id`, true); + formik.setFieldValue( + `deliveries.${deliveryIdx}.supplier_id`, + (val as OptionType)?.value + ); + }, + [] + ); + + const handleDeliveryDocumentChange = useCallback( + (deliveryIdx: number, e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + if (file.size > 5 * 1024 * 1024) { + toast.error('Ukuran dokumen maksimal 5 MB!'); + e.target.value = ''; + return; + } + formik.setFieldValue(`deliveries.${deliveryIdx}.document`, file); + } + }, + [] + ); + + const handleDeliveryCostChange = useCallback((idx: number, value: number) => { + formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value); + + const delivery = formik.values.deliveries?.[idx]; + if (delivery) { + const productQty = delivery.products.reduce( + (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), + 0 + ); + if (productQty > 0 && value > 0) { + const perItem = value / productQty; + formik.setFieldValue( + `deliveries.${idx}.delivery_cost_per_item`, + perItem + ); + } else if (value === 0) { + formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0); + } + } + }, []); + const handleDeliveryCostPerItemChange = useCallback( (idx: number, value: number) => { formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, value); @@ -484,7 +691,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { } } }, - [formik] + [] ); const handleDeliveryCostChangeWrapper = useCallback( @@ -959,72 +1166,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { label='Gudang' placeholder='Pilih gudang asal...' value={formik.values.source_warehouse} - onChange={(val) => { - const newSourceWarehouseId = (val as WarehouseOptionType) - ?.value; - - if (newSourceWarehouseId) { - if ( - newSourceWarehouseId === - formik.values.destination_warehouse_id - ) { - const destinationWarehouseName = - ( - formik.values - .destination_warehouse as WarehouseOptionType - )?.label || 'gudang tujuan'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang asal tidak boleh sama dengan ${destinationWarehouseName}.` - ); - return; - } - } - - formik.setFieldTouched('source_warehouse', true); - formik.setFieldValue('source_warehouse', val); - formik.setFieldTouched('source_warehouse_id', true); - formik.setFieldValue( - 'source_warehouse_id', - newSourceWarehouseId - ); - - if ( - formik.errors.destination_warehouse_id === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - formik.setFieldError('destination_warehouse_id', undefined); - } - - if ( - newSourceWarehouseId && - newSourceWarehouseId !== formik.values.source_warehouse_id - ) { - formik.setFieldValue('products', [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ]); - formik.setFieldTouched('products', false); - - const updatedDeliveries = formik.values.deliveries.map( - (delivery) => ({ - ...delivery, - products: [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ], - }) - ); - formik.setFieldValue('deliveries', updatedDeliveries); - formik.setFieldTouched('deliveries', false); - } - }} + onChange={handleSourceWarehouseChange} options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} onMenuScrollToBottom={loadMoreWarehouses} @@ -1088,41 +1230,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { label='Gudang' placeholder='Pilih gudang tujuan...' value={formik.values.destination_warehouse} - onChange={(val) => { - const newDestinationWarehouseId = (val as WarehouseOptionType) - ?.value; - - if (newDestinationWarehouseId) { - if ( - newDestinationWarehouseId === - formik.values.source_warehouse_id - ) { - const sourceWarehouseName = - (formik.values.source_warehouse as WarehouseOptionType) - ?.label || 'gudang asal'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` - ); - return; - } - } - - formik.setFieldTouched('destination_warehouse', true); - formik.setFieldValue('destination_warehouse', val); - formik.setFieldTouched('destination_warehouse_id', true); - formik.setFieldValue( - 'destination_warehouse_id', - newDestinationWarehouseId - ); - - if ( - formik.errors.destination_warehouse_id === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - formik.setFieldError('destination_warehouse_id', undefined); - } - }} + onChange={handleDestinationWarehouseChange} options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} isLoading={isLoadingWarehouses} @@ -1196,18 +1304,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { selectedProducts.length && formik.values.products?.length > 0 } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedProducts( - formik.values.products?.map((_, idx) => idx) ?? - [] - ); - } else { - setSelectedProducts([]); - } - }} + onChange={handleProductSelectAllChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1244,17 +1341,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { - ) => { - if (e.target.checked) { - setSelectedProducts([...selectedProducts, idx]); - } else { - setSelectedProducts( - selectedProducts.filter((i) => i !== idx) - ); - } - }} + onChange={handleProductCheckboxChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1266,41 +1353,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { { - formik.setFieldTouched( - `products.${idx}.product`, - true - ); - formik.setFieldValue( - `products.${idx}.product`, - val - ); - formik.setFieldTouched( - `products.${idx}.product_id`, - true - ); - formik.setFieldValue( - `products.${idx}.product_id`, - (val as ProductWarehouseOptionType)?.value - ); - - const updatedDeliveries = - formik.values.deliveries.map((delivery) => ({ - ...delivery, - products: [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ], - })); - formik.setFieldValue( - 'deliveries', - updatedDeliveries - ); - formik.setFieldTouched('deliveries', false); - }} + onChange={(val) => handleProductChange(idx, val)} options={productWarehouseOptions} onInputChange={setProductWarehouseSelectInputValue} onMenuScrollToBottom={loadMoreProductWarehouses} @@ -1427,19 +1480,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { selectedDeliveries.length && formik.values.deliveries?.length > 0 } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedDeliveries( - formik.values.deliveries?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedDeliveries([]); - } - }} + onChange={handleDeliverySelectAllChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1522,20 +1563,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { - ) => { - if (e.target.checked) { - setSelectedDeliveries([ - ...selectedDeliveries, - idx, - ]); - } else { - setSelectedDeliveries( - selectedDeliveries.filter((i) => i !== idx) - ); - } - }} + onChange={handleDeliveryCheckboxChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1548,24 +1576,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { required placeholder='Pilih produk...' value={delivery.products[0]?.product ?? undefined} - onChange={(val) => { - formik.setFieldTouched( - `deliveries.${idx}.products.0.product`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.products.0.product`, - val - ); - formik.setFieldTouched( - `deliveries.${idx}.products.0.product_id`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.products.0.product_id`, - (val as OptionType)?.value - ); - }} + onChange={(val) => + handleDeliveryProductChange(idx, val) + } options={getFilteredProductWarehouseOptions()} isDisabled={type === 'detail'} isClearable @@ -1616,24 +1629,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { required placeholder='Pilih supplier...' value={delivery.supplier} - onChange={(val) => { - formik.setFieldTouched( - `deliveries.${idx}.supplier`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.supplier`, - val - ); - formik.setFieldTouched( - `deliveries.${idx}.supplier_id`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.supplier_id`, - (val as OptionType)?.value - ); - }} + onChange={(val) => + handleDeliverySupplierChange(idx, val) + } options={supplierOptions} onInputChange={setSupplierSelectInputValue} isLoading={isLoadingSuppliers} @@ -1725,20 +1723,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { { - const file = e.target.files?.[0]; - if (file) { - if (file.size > 5 * 1024 * 1024) { - toast.error('Ukuran dokumen maksimal 5 MB!'); - e.target.value = ''; - return; - } - formik.setFieldValue( - `deliveries.${idx}.document`, - file - ); - } - }} + onChange={(e) => + handleDeliveryDocumentChange(idx, e) + } {...isRepeaterInputError( 'deliveries', 'document', From fb980c38c9b8c7fda830636f55d4a0196456ac8f Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 10:42:20 +0700 Subject: [PATCH 16/52] refactor(FE): Toast warehouse duplicate error on validation --- .../inventory/movement/form/MovementForm.tsx | 88 ++++--------------- 1 file changed, 19 insertions(+), 69 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 8137260f..77934af5 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -263,6 +263,25 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, }); + // ===== WAREHOUSE VALIDATION TOAST ===== + useEffect(() => { + const destinationWarehouseError = formik.errors + .destination_warehouse_id as string; + + if ( + destinationWarehouseError === + 'Gudang tujuan tidak boleh sama dengan gudang asal!' + ) { + const sourceWarehouseName = + (formik.values.source_warehouse as WarehouseOptionType)?.label || + 'gudang asal'; + + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` + ); + } + }, [formik.errors.destination_warehouse_id, formik.values.source_warehouse]); + // ===== PRODUCT WAREHOUSE FETCHING (after form initialization) ===== const { setInputValue: setProductWarehouseSelectInputValue, @@ -361,59 +380,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { (val: OptionType | OptionType[] | null) => { const newSourceWarehouseId = (val as WarehouseOptionType)?.value; - if (newSourceWarehouseId) { - if (newSourceWarehouseId === formik.values.destination_warehouse_id) { - const destinationWarehouseName = - (formik.values.destination_warehouse as WarehouseOptionType) - ?.label || 'gudang tujuan'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang asal tidak boleh sama dengan ${destinationWarehouseName}.` - ); - return; - } - } - formik.setFieldTouched('source_warehouse', true); formik.setFieldValue('source_warehouse', val); formik.setFieldTouched('source_warehouse_id', true); formik.setFieldValue('source_warehouse_id', newSourceWarehouseId); - - if ( - formik.errors.destination_warehouse_id === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - formik.setFieldError('destination_warehouse_id', undefined); - } - - if ( - newSourceWarehouseId && - newSourceWarehouseId !== formik.values.source_warehouse_id - ) { - formik.setFieldValue('products', [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ]); - formik.setFieldTouched('products', false); - - const updatedDeliveries = formik.values.deliveries.map( - (delivery: DeliverySchema) => ({ - ...delivery, - products: [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ], - }) - ); - formik.setFieldValue('deliveries', updatedDeliveries); - formik.setFieldTouched('deliveries', false); - } }, [] ); @@ -422,19 +392,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { (val: OptionType | OptionType[] | null) => { const newDestinationWarehouseId = (val as WarehouseOptionType)?.value; - if (newDestinationWarehouseId) { - if (newDestinationWarehouseId === formik.values.source_warehouse_id) { - const sourceWarehouseName = - (formik.values.source_warehouse as WarehouseOptionType)?.label || - 'gudang asal'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` - ); - return; - } - } - formik.setFieldTouched('destination_warehouse', true); formik.setFieldValue('destination_warehouse', val); formik.setFieldTouched('destination_warehouse_id', true); @@ -442,13 +399,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { 'destination_warehouse_id', newDestinationWarehouseId ); - - if ( - formik.errors.destination_warehouse_id === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - formik.setFieldError('destination_warehouse_id', undefined); - } }, [] ); From 1c002a1b952253f225883af6bf55241688ed2563 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 10:44:18 +0700 Subject: [PATCH 17/52] refactor(FE): Stop resetting deliveries on product change --- .../inventory/movement/form/MovementForm.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 77934af5..657ca12b 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -445,21 +445,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { `products.${idx}.product_id`, (val as ProductWarehouseOptionType)?.value ); - - const updatedDeliveries = formik.values.deliveries.map( - (delivery: DeliverySchema) => ({ - ...delivery, - products: [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ], - }) - ); - formik.setFieldValue('deliveries', updatedDeliveries); - formik.setFieldTouched('deliveries', false); }, [] ); From 23c758b0cf6a2a40626d6fb33b1697fd549a78fc Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 10:47:12 +0700 Subject: [PATCH 18/52] refactor(FE): Prevent selecting same source and destination --- .../inventory/movement/form/MovementForm.tsx | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 657ca12b..e57ead1e 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -263,25 +263,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, }); - // ===== WAREHOUSE VALIDATION TOAST ===== - useEffect(() => { - const destinationWarehouseError = formik.errors - .destination_warehouse_id as string; - - if ( - destinationWarehouseError === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - const sourceWarehouseName = - (formik.values.source_warehouse as WarehouseOptionType)?.label || - 'gudang asal'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` - ); - } - }, [formik.errors.destination_warehouse_id, formik.values.source_warehouse]); - // ===== PRODUCT WAREHOUSE FETCHING (after form initialization) ===== const { setInputValue: setProductWarehouseSelectInputValue, @@ -380,18 +361,47 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { (val: OptionType | OptionType[] | null) => { const newSourceWarehouseId = (val as WarehouseOptionType)?.value; + if ( + newSourceWarehouseId && + newSourceWarehouseId === formik.values.destination_warehouse_id + ) { + const destinationWarehouseName = + (formik.values.destination_warehouse as WarehouseOptionType)?.label || + 'gudang tujuan'; + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang asal tidak boleh sama dengan ${destinationWarehouseName}.` + ); + return; + } + formik.setFieldTouched('source_warehouse', true); formik.setFieldValue('source_warehouse', val); formik.setFieldTouched('source_warehouse_id', true); formik.setFieldValue('source_warehouse_id', newSourceWarehouseId); }, - [] + [ + formik.values.destination_warehouse_id, + formik.values.destination_warehouse, + ] ); const handleDestinationWarehouseChange = useCallback( (val: OptionType | OptionType[] | null) => { const newDestinationWarehouseId = (val as WarehouseOptionType)?.value; + if ( + newDestinationWarehouseId && + newDestinationWarehouseId === formik.values.source_warehouse_id + ) { + const sourceWarehouseName = + (formik.values.source_warehouse as WarehouseOptionType)?.label || + 'gudang asal'; + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` + ); + return; + } + formik.setFieldTouched('destination_warehouse', true); formik.setFieldValue('destination_warehouse', val); formik.setFieldTouched('destination_warehouse_id', true); @@ -400,7 +410,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { newDestinationWarehouseId ); }, - [] + [formik.values.source_warehouse_id, formik.values.source_warehouse] ); const addProduct = useCallback(() => { From 24ff7a080f1d6aeb40a182c2585f84a2a47d6607 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 13:03:07 +0700 Subject: [PATCH 19/52] refactor(FE): Reset products when source warehouse changes --- .../inventory/movement/form/MovementForm.tsx | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index e57ead1e..d36fb067 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useFormik } from 'formik'; import useSWR from 'swr'; @@ -263,6 +263,47 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, }); + const prevSourceWarehouseIdRef = useRef( + formik.values.source_warehouse_id + ); + + // ===== RESET PRODUCTS WHEN SOURCE WAREHOUSE CHANGES ===== + useEffect(() => { + const prevSourceWarehouseId = prevSourceWarehouseIdRef.current; + const currentSourceWarehouseId = formik.values.source_warehouse_id; + + if ( + prevSourceWarehouseId !== currentSourceWarehouseId && + prevSourceWarehouseId !== null + ) { + formik.setFieldValue('products', [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ]); + formik.setFieldTouched('products', false); + + const updatedDeliveries = formik.values.deliveries.map( + (delivery: DeliverySchema) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + }) + ); + formik.setFieldValue('deliveries', updatedDeliveries); + formik.setFieldTouched('deliveries', false); + } + + prevSourceWarehouseIdRef.current = currentSourceWarehouseId; + }, [formik.values.source_warehouse_id, formik.values.deliveries]); + // ===== PRODUCT WAREHOUSE FETCHING (after form initialization) ===== const { setInputValue: setProductWarehouseSelectInputValue, From 56d4eca03457999700ac0ba77501279f591ee742 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 13:13:23 +0700 Subject: [PATCH 20/52] refactor(FE): Validate weight max is not less than min --- .../report/sale/tab/HppPerKandangTab.tsx | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index 22d80220..df2f2ddc 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -40,6 +40,9 @@ const HppPerKandangTab = () => { // ===== SUBMISSION STATE ===== const [isSubmitted, setIsSubmitted] = useState(false); + // ===== VALIDATION STATE ===== + const [weightMaxError, setWeightMaxError] = useState(''); + // ===== TABLE FILTER STATE ===== const { state: tableFilterState, updateFilter } = useTableFilter({ initial: { @@ -127,8 +130,12 @@ const HppPerKandangTab = () => { const val = e.target.value; updateFilter('weight_min', val ? String(parseFloat(val) || 0) : ''); setIsSubmitted(false); + + if (weightMaxError) { + setWeightMaxError(''); + } }, - [updateFilter] + [updateFilter, weightMaxError] ); const weightMaxChangeHandler = useCallback< @@ -136,10 +143,22 @@ const HppPerKandangTab = () => { >( (e) => { const val = e.target.value; - updateFilter('weight_max', val ? String(parseFloat(val) || 0) : ''); + const weightMax = val ? parseFloat(val) || 0 : 0; + const weightMin = tableFilterState.weight_min + ? parseFloat(tableFilterState.weight_min) + : 0; + + if (weightMax < weightMin) { + setWeightMaxError('Rentang bobot max tidak boleh lebih kecil dari min'); + toast.error('Rentang bobot max tidak boleh lebih kecil dari min'); + return; + } + + setWeightMaxError(''); + updateFilter('weight_max', val ? String(weightMax) : ''); setIsSubmitted(false); }, - [updateFilter] + [updateFilter, tableFilterState.weight_min] ); const periodChangeHandler = useCallback>( @@ -792,6 +811,8 @@ const HppPerKandangTab = () => { placeholder='Masukkan bobot maximum' value={tableFilterState.weight_max} onChange={weightMaxChangeHandler} + isError={!!weightMaxError} + errorMessage={weightMaxError} />
{
- From f4166f4dbdf12b596d0763f7bd36cb3fb1a4e814 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 13:41:15 +0700 Subject: [PATCH 21/52] refactor(FE): Keep select menus open and show selected options --- src/components/pages/report/sale/tab/HppPerKandangTab.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index df2f2ddc..d7ac536a 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -760,6 +760,8 @@ const HppPerKandangTab = () => { onInputChange={setAreaInputValue} onMenuScrollToBottom={loadMoreAreas} isLoading={isLoadingAreas} + closeMenuOnSelect={false} + hideSelectedOptions={false} isClearable /> { onInputChange={setLocationInputValue} onMenuScrollToBottom={loadMoreLocations} isLoading={isLoadingLocations} + closeMenuOnSelect={false} + hideSelectedOptions={false} isClearable /> { onInputChange={setKandangInputValue} onMenuScrollToBottom={loadMoreKandangs} isLoading={isLoadingKandangs} + closeMenuOnSelect={false} + hideSelectedOptions={false} isClearable />
From 366864582f665f9c076f7a140b3b7c2934e14a78 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 14:14:04 +0700 Subject: [PATCH 22/52] feat(FE): Add TOTAL row to HppPerkandang PDF export --- .../sale/export/HppPerkandangExport.tsx | 139 +++++++++++++++++- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx index 95f42df6..c184d49d 100644 --- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -356,12 +356,7 @@ const createPDFDocument = ( {data.rows.map((item: HppPerKandangRow, index: number) => ( {index + 1} @@ -415,6 +410,138 @@ const createPDFDocument = ( ))} + + {/* TOTAL Row */} + {data.summary?.total && ( + + + TOTAL + + + ALL + + + - + + + + {formatNumber(data.summary.total.average_weight_kg)} + + + + + {formatNumber( + data.summary.total.total_egg_production_pieces + )} + + + + + {formatNumber(data.summary.total.total_egg_production_kg)} + + + + + {data.rows + .flatMap((row: HppPerKandangRow) => + row.feed_suppliers?.map( + (s: { alias?: string; name: string }) => + s.alias || s.name + ) + ) + .filter((v: string, i: number, a: string[]) => a.indexOf(v) === i) + .join(' | ') || '-'} + + + + + {data.rows + .flatMap((row: HppPerKandangRow) => + row.doc_suppliers?.map( + (s: { alias?: string; name: string }) => + s.alias || s.name + ) + ) + .filter((v: string, i: number, a: string[]) => a.indexOf(v) === i) + .join(' | ') || '-'} + + + + + {formatCurrency( + data.summary.total.total_average_doc_price_rp + )} + + + + + {formatCurrency( + data.summary.total.average_egg_hpp_rp_per_kg + )} + + + + + {formatCurrency(data.summary.total.total_egg_value_rp)} + + + + )} From 200290a0b32b307bec4ec93978db61c5bd5ad027 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 14:27:27 +0700 Subject: [PATCH 23/52] refactor(FE): Remove header cell bottom and right borders in PDF export --- .../sale/export/HppPerkandangExport.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx index c184d49d..62b1faf5 100644 --- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -246,7 +246,7 @@ const createPDFDocument = ( HPP Telur (RP/KG) - + Nominal Sisa @@ -301,7 +301,7 @@ const createPDFDocument = ( {formatCurrency(group.egg_hpp_rp_per_kg)} - + {formatCurrency(group.egg_value_rp)} @@ -347,7 +347,7 @@ const createPDFDocument = ( HPP Telur (RP/KG) - + Nominal Sisa @@ -405,7 +405,7 @@ const createPDFDocument = ( {formatCurrency(item.egg_hpp_rp_per_kg)} - + {formatCurrency(item.egg_value_rp)} @@ -417,7 +417,7 @@ const createPDFDocument = ( TOTAL @@ -425,7 +425,7 @@ const createPDFDocument = ( ALL @@ -433,7 +433,7 @@ const createPDFDocument = ( - @@ -441,7 +441,7 @@ const createPDFDocument = ( @@ -451,7 +451,7 @@ const createPDFDocument = ( @@ -463,7 +463,7 @@ const createPDFDocument = ( @@ -473,7 +473,7 @@ const createPDFDocument = ( @@ -484,14 +484,17 @@ const createPDFDocument = ( s.alias || s.name ) ) - .filter((v: string, i: number, a: string[]) => a.indexOf(v) === i) + .filter( + (v: string, i: number, a: string[]) => + a.indexOf(v) === i + ) .join(' | ') || '-'} @@ -502,14 +505,17 @@ const createPDFDocument = ( s.alias || s.name ) ) - .filter((v: string, i: number, a: string[]) => a.indexOf(v) === i) + .filter( + (v: string, i: number, a: string[]) => + a.indexOf(v) === i + ) .join(' | ') || '-'} @@ -521,7 +527,7 @@ const createPDFDocument = ( @@ -533,7 +539,7 @@ const createPDFDocument = ( From da040a4f7ef6a964ce47acb15fa8db448d43c107 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 14:36:21 +0700 Subject: [PATCH 24/52] refactor(FE): Format style objects in HppPerkandangExport --- .../sale/export/HppPerkandangExport.tsx | 95 ++++++++++++++++--- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx index 62b1faf5..9b05a88d 100644 --- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -246,7 +246,12 @@ const createPDFDocument = ( HPP Telur (RP/KG) - + Nominal Sisa @@ -301,7 +306,12 @@ const createPDFDocument = ( {formatCurrency(group.egg_hpp_rp_per_kg)} - + {formatCurrency(group.egg_value_rp)} @@ -347,7 +357,12 @@ const createPDFDocument = ( HPP Telur (RP/KG) - + Nominal Sisa @@ -405,7 +420,12 @@ const createPDFDocument = ( {formatCurrency(item.egg_hpp_rp_per_kg)} - + {formatCurrency(item.egg_value_rp)} @@ -417,7 +437,11 @@ const createPDFDocument = ( TOTAL @@ -425,7 +449,11 @@ const createPDFDocument = ( ALL @@ -433,7 +461,11 @@ const createPDFDocument = ( - @@ -441,7 +473,11 @@ const createPDFDocument = ( @@ -451,7 +487,11 @@ const createPDFDocument = ( @@ -463,7 +503,11 @@ const createPDFDocument = ( @@ -473,7 +517,11 @@ const createPDFDocument = ( @@ -494,7 +542,11 @@ const createPDFDocument = ( @@ -515,7 +567,11 @@ const createPDFDocument = ( @@ -527,7 +583,11 @@ const createPDFDocument = ( @@ -539,7 +599,12 @@ const createPDFDocument = ( From b3f8fc451ddfc470885144781839c0e43d123654 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 14:37:23 +0700 Subject: [PATCH 25/52] feat(FE): Add Rekapitulasi sheet to Excel export --- .../report/sale/tab/HppPerKandangTab.tsx | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index d7ac536a..eb66e0c4 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -344,8 +344,53 @@ const HppPerKandangTab = () => { const allExportData = allDataForExport.rows as HppPerKandangReport['rows']; + const perWeightRangeSummary = + allDataForExport.summary.per_weight_range || []; + const summaryTotal = allDataForExport.summary.total; + const rekapitulasiData: { [key: string]: string | number }[] = + perWeightRangeSummary.map( + (item: HppPerKandangPerWeightRange, index: number) => ({ + No: index + 1, + 'Rentang BW': item.label || '', + 'Sisa Butir': item.egg_production_pieces || 0, + 'Sisa Kg': item.egg_production_kg || 0, + 'Rata-Rata Bobot (Kg)': item.avg_weight_kg || 0, + 'Feed (Supplier)': + item.feed_suppliers + ?.map( + (s: { alias?: string; name: string }) => s.alias || s.name + ) + .join(' | ') || '', + 'DOC (Supplier)': + item.doc_suppliers + ?.map( + (s: { alias?: string; name: string }) => s.alias || s.name + ) + .join(' | ') || '', + 'Rata-Rata Harga DOC': item.average_doc_price_rp || 0, + 'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0, + 'Nominal Sisa': item.egg_value_rp || 0, + }) + ); + + const rekapitulasiWorksheet = XLSX.utils.json_to_sheet(rekapitulasiData); + + const rekapitulasiColWidths = [ + { wch: 5 }, // No + { wch: 15 }, // Rentang BW + { wch: 15 }, // Sisa Butir + { wch: 12 }, // Sisa Kg + { wch: 18 }, // Rata-Rata Bobot (Kg) + { wch: 20 }, // Feed (Supplier) + { wch: 20 }, // DOC (Supplier) + { wch: 20 }, // Rata-Rata Harga DOC + { wch: 18 }, // HPP Telur (RP/KG) + { wch: 25 }, // Nominal Sisa + ]; + rekapitulasiWorksheet['!cols'] = rekapitulasiColWidths; + const excelData: { [key: string]: string | number }[] = allExportData.map( (item: HppPerKandangRow, index: number) => ({ No: index + 1, @@ -403,7 +448,12 @@ const HppPerKandangTab = () => { worksheet['!cols'] = colWidths; const workbook = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(workbook, worksheet, 'HPP Per Kandang'); + XLSX.utils.book_append_sheet( + workbook, + rekapitulasiWorksheet, + 'Rekapitulasi' + ); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Detail Per Kandang'); const filename = `laporan-hpp-harian-kandang-periode-${tableFilterState.period}.xlsx`; From d9c154997dee2bcc85696fbbc3003e28646a07e7 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 19 Jan 2026 14:41:20 +0700 Subject: [PATCH 26/52] feat: create Closing Sapronak Summary component --- .../ClosingIncomingSapronaksSummaryTable.tsx | 174 ++++++++++++++++++ .../ClosingOutgoingSapronaksSummaryTable.tsx | 174 ++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 src/components/pages/closing/ClosingIncomingSapronaksSummaryTable.tsx create mode 100644 src/components/pages/closing/ClosingOutgoingSapronaksSummaryTable.tsx diff --git a/src/components/pages/closing/ClosingIncomingSapronaksSummaryTable.tsx b/src/components/pages/closing/ClosingIncomingSapronaksSummaryTable.tsx new file mode 100644 index 00000000..49e4f108 --- /dev/null +++ b/src/components/pages/closing/ClosingIncomingSapronaksSummaryTable.tsx @@ -0,0 +1,174 @@ +'use client'; + +import { ChangeEventHandler, useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; +import { ColumnDef, SortingState } from '@tanstack/react-table'; + +import { Icon } from '@iconify/react'; +import Table from '@/components/Table'; +import Card from '@/components/Card'; +import Collapse from '@/components/Collapse'; + +import { cn, formatNumber } from '@/lib/helper'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { ClosingApi } from '@/services/api/closing'; +import { ClosingIncomingSapronakSummary } from '@/types/api/closing'; + +interface ClosingIncomingSapronaksSummaryTableProps { + projectFlockId: number; +} + +const ClosingIncomingSapronaksSummaryTable = ({ + projectFlockId, +}: ClosingIncomingSapronaksSummaryTableProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + nameSort: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + }, + }); + + const { + data: incomingSapronakSummaries, + isLoading: isLoadingIncomingSapronakSummaries, + } = useSWR( + `${ClosingApi.basePath}/${projectFlockId}/sapronak/summary${getTableFilterQueryString()}&type=incoming&kandang_id=${kandangId ? `${kandangId}` : ''}`, + ClosingApi.getAllIncomingSapronakSummaryFetcher, + { + keepPreviousData: true, + } + ); + + const [open, setOpen] = useState(true); + + const [sorting, setSorting] = useState([]); + const [rowSelection, setRowSelection] = useState>({}); + + const incomingSapronaksColumns: ColumnDef[] = + [ + { + header: '#', + cell: (props) => props.row.index + 1, + }, + { + accessorKey: 'category', + header: 'Kategori', + }, + { + accessorKey: 'total_qty', + header: 'Total Kuantitas', + cell: (props) => + `${formatNumber(props.row.original.total_qty)} ${props.row.original.uom.name}`, + }, + ]; + + const searchChangeHandler: ChangeEventHandler = (e) => { + updateFilter('search', e.target.value); + }; + + // track sorting + useEffect(() => { + const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); + + if (!isNameSorted) { + updateFilter('nameSort', ''); + } else { + updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); + } + }, [sorting, updateFilter]); + + useEffect(() => { + if (!open) { + setOpen( + isResponseSuccess(incomingSapronakSummaries) + ? incomingSapronakSummaries.data.length > 0 + : false + ); + } + }, [incomingSapronakSummaries, isResponseSuccess]); + + return ( + + +
Ringkasan Sapronak Masuk
+ + +
+ } + className='w-full!' + titleClassName='w-full p-0!' + > +
+ + data={ + isResponseSuccess(incomingSapronakSummaries) + ? incomingSapronakSummaries?.data + : [] + } + columns={incomingSapronaksColumns} + pageSize={tableFilterState.pageSize} + onPageSizeChange={setPageSize} + rowOptions={[10, 20, 50, 100]} + page={ + isResponseSuccess(incomingSapronakSummaries) + ? incomingSapronakSummaries?.meta?.page + : 0 + } + totalItems={ + isResponseSuccess(incomingSapronakSummaries) + ? incomingSapronakSummaries?.meta?.total_results + : 0 + } + onPageChange={setPage} + isLoading={isLoadingIncomingSapronakSummaries} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn({ + 'w-full mb-20': + isResponseSuccess(incomingSapronakSummaries) && + incomingSapronakSummaries?.data?.length === 0, + }), + }} + /> +
+ + + ); +}; + +export default ClosingIncomingSapronaksSummaryTable; diff --git a/src/components/pages/closing/ClosingOutgoingSapronaksSummaryTable.tsx b/src/components/pages/closing/ClosingOutgoingSapronaksSummaryTable.tsx new file mode 100644 index 00000000..42fcb588 --- /dev/null +++ b/src/components/pages/closing/ClosingOutgoingSapronaksSummaryTable.tsx @@ -0,0 +1,174 @@ +'use client'; + +import { ChangeEventHandler, useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; +import { ColumnDef, SortingState } from '@tanstack/react-table'; + +import { Icon } from '@iconify/react'; +import Table from '@/components/Table'; +import Card from '@/components/Card'; +import Collapse from '@/components/Collapse'; + +import { cn, formatNumber } from '@/lib/helper'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { ClosingApi } from '@/services/api/closing'; +import { ClosingOutgoingSapronakSummary } from '@/types/api/closing'; + +interface ClosingOutgoingSapronaksSummaryTableProps { + projectFlockId: number; +} + +const ClosingOutgoingSapronaksSummaryTable = ({ + projectFlockId, +}: ClosingOutgoingSapronaksSummaryTableProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + nameSort: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + }, + }); + + const { + data: outgoingSapronakSummaries, + isLoading: isLoadingOutgoingSapronakSummaries, + } = useSWR( + `${ClosingApi.basePath}/${projectFlockId}/sapronak/summary${getTableFilterQueryString()}&type=outgoing&kandang_id=${kandangId ? `${kandangId}` : ''}`, + ClosingApi.getAllIncomingSapronakSummaryFetcher, + { + keepPreviousData: true, + } + ); + + const [open, setOpen] = useState(true); + + const [sorting, setSorting] = useState([]); + const [rowSelection, setRowSelection] = useState>({}); + + const outgoingSapronaksColumns: ColumnDef[] = + [ + { + header: '#', + cell: (props) => props.row.index + 1, + }, + { + accessorKey: 'category', + header: 'Kategori', + }, + { + accessorKey: 'total_qty', + header: 'Total Kuantitas', + cell: (props) => + `${formatNumber(props.row.original.total_qty)} ${props.row.original.uom.name}`, + }, + ]; + + const searchChangeHandler: ChangeEventHandler = (e) => { + updateFilter('search', e.target.value); + }; + + // track sorting + useEffect(() => { + const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); + + if (!isNameSorted) { + updateFilter('nameSort', ''); + } else { + updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); + } + }, [sorting, updateFilter]); + + useEffect(() => { + if (!open) { + setOpen( + isResponseSuccess(outgoingSapronakSummaries) + ? outgoingSapronakSummaries.data.length > 0 + : false + ); + } + }, [outgoingSapronakSummaries, isResponseSuccess]); + + return ( + + +
Ringkasan Sapronak Keluar
+ + +
+ } + className='w-full!' + titleClassName='w-full p-0!' + > +
+ + data={ + isResponseSuccess(outgoingSapronakSummaries) + ? outgoingSapronakSummaries?.data + : [] + } + columns={outgoingSapronaksColumns} + pageSize={tableFilterState.pageSize} + onPageSizeChange={setPageSize} + rowOptions={[10, 20, 50, 100]} + page={ + isResponseSuccess(outgoingSapronakSummaries) + ? outgoingSapronakSummaries?.meta?.page + : 0 + } + totalItems={ + isResponseSuccess(outgoingSapronakSummaries) + ? outgoingSapronakSummaries?.meta?.total_results + : 0 + } + onPageChange={setPage} + isLoading={isLoadingOutgoingSapronakSummaries} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn({ + 'w-full mb-20': + isResponseSuccess(outgoingSapronakSummaries) && + outgoingSapronakSummaries?.data?.length === 0, + }), + }} + /> +
+ + + ); +}; + +export default ClosingOutgoingSapronaksSummaryTable; From 0f64baca230c2582e71d60fc041755bad67f5858 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 19 Jan 2026 14:42:04 +0700 Subject: [PATCH 27/52] feat: show Closing Sapronak Summary table --- .../pages/closing/ClosingSapronakTabContent.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/pages/closing/ClosingSapronakTabContent.tsx b/src/components/pages/closing/ClosingSapronakTabContent.tsx index 41c7aa05..03c3c984 100644 --- a/src/components/pages/closing/ClosingSapronakTabContent.tsx +++ b/src/components/pages/closing/ClosingSapronakTabContent.tsx @@ -2,6 +2,8 @@ import ClosingIncomingSapronaksTable from '@/components/pages/closing/ClosingIncomingSapronaksTable'; import ClosingOutgoingSapronaksTable from '@/components/pages/closing/ClosingOutgoingSapronaksTable'; +import ClosingIncomingSapronaksSummaryTable from '@/components/pages/closing/ClosingIncomingSapronaksSummaryTable'; +import ClosingOutgoingSapronaksSummaryTable from './ClosingOutgoingSapronaksSummaryTable'; interface ClosingSapronakTableProps { projectFlockId?: number; @@ -16,7 +18,15 @@ const ClosingSapronakTabContent = ({ <> + + + + )}
From 949b5cbc1232cc2d0d3a9f3cd5ffc0fa7cd389c6 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 19 Jan 2026 14:44:35 +0700 Subject: [PATCH 28/52] feat: create Closing Sapronak Summary type --- src/types/api/closing.d.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index ff35fd28..ec256a45 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -11,6 +11,7 @@ import { Product } from '@type/api/master-data/product'; import { Customer } from '@type/api/master-data/customer'; import { BaseMetadata } from '@/types/api/api-general'; import { ProjectFlock } from '@/types/api/production/project-flock'; +import { BaseUom } from '@/types/api/master-data/uom'; export type BaseSales = { id: number; @@ -104,8 +105,16 @@ export type ClosingIncomingSapronak = { notes: string; }; +export type ClosingIncomingSapronakSummary = { + category: string; + total_qty: number; + uom: BaseUom; +}; + export type ClosingOutgoingSapronak = ClosingIncomingSapronak; +export type ClosingOutgoingSapronakSummary = ClosingIncomingSapronakSummary; + export type ClosingProductionData = { purchase: { initial_population: number; From e143668f825a1997357790d28c8f43697e19381a Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 19 Jan 2026 14:46:29 +0700 Subject: [PATCH 29/52] feat: create getAllIncomingSapronakSummaryFetcher and getAllOutgoingSapronakSummaryFetcher method --- src/services/api/closing.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 323e09e8..7462e41a 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -11,6 +11,8 @@ import { ClosingSapronakCalculation, ClosingProductionData, ClosingHppExpedition, + ClosingIncomingSapronakSummary, + ClosingOutgoingSapronakSummary, } from '@/types/api/closing'; import { BaseApiResponse } from '@/types/api/api-general'; import { httpClient, httpClientFetcher } from '@/services/http/client'; @@ -62,6 +64,14 @@ export class ClosingApiService extends BaseApiService { ); } + async getAllIncomingSapronakSummaryFetcher( + endpoint: string + ): Promise> { + return await httpClientFetcher< + BaseApiResponse + >(endpoint); + } + async getAllOutgoingSapronakFetcher( endpoint: string ): Promise> { @@ -70,6 +80,14 @@ export class ClosingApiService extends BaseApiService { ); } + async getAllOutgoingSapronakSummaryFetcher( + endpoint: string + ): Promise> { + return await httpClientFetcher< + BaseApiResponse + >(endpoint); + } + async getGeneralInfo( id: number ): Promise | undefined> { From eefec938113f1c687227b9fcb87f3df60a0ff67e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 14:59:26 +0700 Subject: [PATCH 30/52] refactor(FE): Add Kandang and Location to Recording type --- src/types/api/production/recording.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index e30abe4c..928afae0 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -1,6 +1,8 @@ import { BaseApproval, BaseMetadata, User } from '@/types/api/api-general'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { Warehouse } from '@/types/api/master-data/warehouse'; +import { Kandang } from '@/types/api/master-data/kandang'; +import { Location } from '@/types/api/master-data/location'; export type ProductionStandard = { id: number; @@ -87,6 +89,8 @@ export type Recording = BaseMetadata & approval?: BaseApproval; created_user: User; warehouse?: Warehouse; + kandang?: Kandang; + location?: Location; product_category?: 'GROWING' | 'LAYING'; depletions?: RecordingDepletion[]; stocks?: RecordingStock[]; From 9c540e7cd80f2beff0e8a18989afcaaba8d6af4d Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 15:27:49 +0700 Subject: [PATCH 31/52] refactor(FE): Add guards for latest_approval and move search --- .../pages/expense/ExpensesTable.tsx | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index fdfd9cc3..895c0997 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -54,17 +54,19 @@ const RowOptionsMenu = ({ rejectClickHandler: () => void; deleteClickHandler: () => void; }) => { - const showEditButton = - props.row.original.latest_approval.step_number !== 6 && - (props.row.original.latest_approval.step_number === 1 || - props.row.original.latest_approval.step_number === 2 || - props.row.original.latest_approval.step_number === 3 || - props.row.original.latest_approval.step_number === 4); + const showEditButton = props.row.original.latest_approval + ? props.row.original.latest_approval.step_number !== 6 && + (props.row.original.latest_approval.step_number === 1 || + props.row.original.latest_approval.step_number === 2 || + props.row.original.latest_approval.step_number === 3 || + props.row.original.latest_approval.step_number === 4) + : false; // TODO: apply RBAC - const showRealizationButton = - props.row.original.latest_approval.action !== 'REJECTED' && - props.row.original.latest_approval.step_number === 4; + const showRealizationButton = props.row.original.latest_approval + ? props.row.original.latest_approval.action !== 'REJECTED' && + props.row.original.latest_approval.step_number === 4 + : false; return ( @@ -278,6 +280,7 @@ const ExpensesTable = () => { cell: ({ row }) => { const isCheckboxDisabled = !row.getCanSelect() || + !row.original.latest_approval || row.original.latest_approval.action === 'REJECTED'; return ( @@ -413,6 +416,8 @@ const ExpensesTable = () => { const tableEnableRowSelectionHandler: (row: Row) => boolean = ( row ) => { + if (!row.original.latest_approval) return false; + return ( row.original.latest_approval.action !== 'REJECTED' && row.original.latest_approval.step_number !== 6 @@ -692,14 +697,6 @@ const ExpensesTable = () => { )}
- -
@@ -753,17 +750,12 @@ const ExpensesTable = () => { }} /> -
From 18f3295562729440a81fb64da4f330795d170b2b Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 19 Jan 2026 15:28:16 +0700 Subject: [PATCH 32/52] fix(FE): fixing initial state in form recording & refactor formik message errors list --- .../recording/form/RecordingForm.tsx | 85 +++++++++++++------ 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index d7f913e8..5044dec5 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -79,6 +79,7 @@ import { GROWING_RECORDING_APPROVAL_LINE, LAYING_RECORDING_APPROVAL_LINE, } from '@/config/approval-line'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface RecordingFormProps { type?: 'add' | 'edit' | 'detail'; @@ -227,7 +228,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [, setApprovalNotes] = useState(''); const [recordingFormErrorMessage, setRecordingFormErrorMessage] = useState(''); - const [formErrorList, setFormErrorList] = useState([]); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [, setNewRecordingData] = useState(null); const [nextDayRecording, setNextDayRecording] = @@ -905,10 +905,58 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { baseValues = getRecordingGrowingFormInitialValues(initialValues); } + if (type === 'add') { + baseValues.location = selectedLocation + ? { + value: Number(selectedLocation.value), + label: selectedLocation.label, + } + : null; + baseValues.location_id = selectedLocation + ? Number(selectedLocation.value) + : 0; + baseValues.project_flock = selectedProjectFlock + ? { + value: Number(selectedProjectFlock.value), + label: selectedProjectFlock.label, + } + : null; + baseValues.project_flock_id = selectedProjectFlock + ? Number(selectedProjectFlock.value) + : 0; + baseValues.kandang = selectedKandang + ? { + value: Number(selectedKandang.value), + label: selectedKandang.label, + } + : null; + baseValues.kandang_id = selectedKandang + ? Number(selectedKandang.value) + : 0; + } + if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) { - baseValues.project_flock_kandang = { - value: projectFlockKandangDetail.project_flock.id, - label: projectFlockKandangDetail.project_flock.flock_name || '', + baseValues = { + ...baseValues, + project_flock_kandang: { + value: projectFlockKandangDetail.project_flock?.id, + label: projectFlockKandangDetail.project_flock?.flock_name || '', + }, + project_flock: { + value: projectFlockKandangDetail.project_flock?.id, + label: projectFlockKandangDetail.project_flock?.flock_name || '', + }, + project_flock_id: projectFlockKandangDetail.project_flock?.id, + location: { + value: projectFlockKandangDetail.project_flock?.location?.id, + label: projectFlockKandangDetail.project_flock?.location?.name || '', + }, + location_id: projectFlockKandangDetail.project_flock?.location?.id, + kandang: { + value: projectFlockKandangDetail.kandang?.id, + label: projectFlockKandangDetail.kandang?.name || '', + }, + kandang_id: projectFlockKandangDetail.kandang?.id, }; } @@ -995,22 +1043,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, }); - const handleValidateForm = async () => { - const errors = await formik.validateForm(); - - if (Object.keys(errors).length > 0) { - const errorMessages = getUniqueFormikErrors(errors); - setFormErrorList(errorMessages); - return; - } - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - handleValidateForm(); - formik.handleSubmit(e); - }; - // ===== HELPER FUNCTIONS ===== const getAvailableStock = useCallback( (productWarehouseId: number) => { @@ -1266,6 +1298,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { [formik, duplicateErrorShown] ); + const { formErrorList, handleFormSubmit, close } = useFormikErrorList(formik); + useEffect(() => { if (projectFlockKandangLookup?.project_flock_kandang_id) { const projectFlockKandangId = @@ -1655,12 +1689,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* Error List Alert */} {formErrorList.length > 0 && ( - setFormErrorList([])} - /> + )} +
+ {JSON.stringify(formik.errors)} +
+
+ {JSON.stringify(formik.values)} +
{/* Basic Info Card */} {(type === 'add' || type === 'edit') && ( Date: Mon, 19 Jan 2026 15:49:31 +0700 Subject: [PATCH 33/52] fix(FE): fixing component date input, remove alert native function & remove console.error --- src/components/input/DateInput.tsx | 20 +++++++++++++++++-- .../pages/dashboard/export/DashboardPDF.ts | 1 - .../marketing/pdf/DeliveryOrderExport.tsx | 6 +++--- .../pages/marketing/pdf/SalesOrderExport.tsx | 6 +++--- src/services/api/marketing/marketing.ts | 9 +++------ src/services/api/production/chickin.ts | 3 +-- .../slices/dashboard-filter.slice.ts | 2 -- 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/components/input/DateInput.tsx b/src/components/input/DateInput.tsx index 2d55fe6d..a424d723 100644 --- a/src/components/input/DateInput.tsx +++ b/src/components/input/DateInput.tsx @@ -113,7 +113,15 @@ const DateInput = ({ }; const handleSelectSingle = (selectedDate?: Date) => { - if (!selectedDate) return; + if (!selectedDate) { + setSelected(undefined); + setDisplayValue(''); + const syntheticEvent = { + target: { name, value: '' }, + } as unknown as React.ChangeEvent; + onChange?.(syntheticEvent); + return; + } if (minDate && selectedDate < minDate) { setInternalError(`Tanggal tidak boleh sebelum ${min}`); return; @@ -136,7 +144,15 @@ const DateInput = ({ }; const handleSelectRange = (range?: { from?: Date; to?: Date }) => { - if (!range) return; + if (!range) { + setSelectedRange({}); + setDisplayValue(''); + const syntheticEvent = { + target: { name, value: { from: '', to: '' } }, + } as unknown as React.ChangeEvent; + onChange?.(syntheticEvent); + return; + } setSelectedRange(range); const fromStr = range.from ? formatDate(range.from, 'DD/MM/YYYY') : ''; diff --git a/src/components/pages/dashboard/export/DashboardPDF.ts b/src/components/pages/dashboard/export/DashboardPDF.ts index dfb1a766..17c5bde4 100644 --- a/src/components/pages/dashboard/export/DashboardPDF.ts +++ b/src/components/pages/dashboard/export/DashboardPDF.ts @@ -253,7 +253,6 @@ export const generateDashboardPDF = async ({ toast.success('PDF exported successfully!', { id: 'export-pdf' }); } catch (error) { - console.error('Error generating PDF:', error); toast.error('Failed to export PDF. Please try again.', { id: 'export-pdf', }); diff --git a/src/components/pages/marketing/pdf/DeliveryOrderExport.tsx b/src/components/pages/marketing/pdf/DeliveryOrderExport.tsx index 46e85a23..4659c650 100644 --- a/src/components/pages/marketing/pdf/DeliveryOrderExport.tsx +++ b/src/components/pages/marketing/pdf/DeliveryOrderExport.tsx @@ -7,6 +7,7 @@ import { formatDate, formatNumber, formatVechicleNumber } from '@/lib/helper'; import { format } from 'path'; import { date } from 'yup'; import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles'; +import toast from 'react-hot-toast'; interface DeliveryOrderExportProps { data?: Marketing; @@ -23,7 +24,7 @@ const DeliveryOrderExport = ({ const handleDownloadPDF = async () => { if (!salesData) { - alert('No sales order data available'); + toast.error('No sales order data available'); return; } setIsGeneratingPDF(true); @@ -40,8 +41,7 @@ const DeliveryOrderExport = ({ document.body.removeChild(link); URL.revokeObjectURL(url); } catch (error) { - console.error('Error generating PDF:', error); - alert('Failed to generate PDF. Please try again.'); + toast.error('Failed to generate PDF. Please try again.'); } finally { setIsGeneratingPDF(false); } diff --git a/src/components/pages/marketing/pdf/SalesOrderExport.tsx b/src/components/pages/marketing/pdf/SalesOrderExport.tsx index f9f0a6c5..5d892853 100644 --- a/src/components/pages/marketing/pdf/SalesOrderExport.tsx +++ b/src/components/pages/marketing/pdf/SalesOrderExport.tsx @@ -5,6 +5,7 @@ import { Document, Image, Page, pdf, Text, View } from '@react-pdf/renderer'; import { useMemo, useState } from 'react'; import { formatDate, formatNumber } from '@/lib/helper'; import pdfStyles from '@/components/pages/marketing/pdf/styles/MarketingPDFStyles'; +import toast from 'react-hot-toast'; interface SalesOrderExportProps { data?: Marketing; @@ -17,7 +18,7 @@ const SalesOrderExport = ({ data }: SalesOrderExportProps) => { const handleDownloadPDF = async () => { if (!salesData) { - alert('No sales order data available'); + toast.error('No sales order data available'); return; } setIsGeneratingPDF(true); @@ -32,8 +33,7 @@ const SalesOrderExport = ({ data }: SalesOrderExportProps) => { document.body.removeChild(link); URL.revokeObjectURL(url); } catch (error) { - console.error('Error generating PDF:', error); - alert('Failed to generate PDF. Please try again.'); + toast.error('Failed to generate PDF. Please try again.'); } finally { setIsGeneratingPDF(false); } diff --git a/src/services/api/marketing/marketing.ts b/src/services/api/marketing/marketing.ts index 59b9b4c8..05afaa30 100644 --- a/src/services/api/marketing/marketing.ts +++ b/src/services/api/marketing/marketing.ts @@ -48,8 +48,7 @@ export class SalesOrderService extends BaseApiService< }, }); } catch (error) { - console.error('Error approve marketing:', error); - return undefined; + throw error; } } @@ -72,8 +71,7 @@ export class SalesOrderService extends BaseApiService< }, }); } catch (error) { - console.error('Error bulk approve marketing:', error); - return undefined; + throw error; } } @@ -95,8 +93,7 @@ export class SalesOrderService extends BaseApiService< }, }); } catch (error) { - console.error('Error delivery marketing:', error); - return undefined; + throw error; } } } diff --git a/src/services/api/production/chickin.ts b/src/services/api/production/chickin.ts index f582c5d4..0efaa0f9 100644 --- a/src/services/api/production/chickin.ts +++ b/src/services/api/production/chickin.ts @@ -35,8 +35,7 @@ export class ChickinService extends BaseApiService< }, }); } catch (error) { - console.error('Error approve chickin:', error); - return undefined; + throw error; } } } diff --git a/src/stores/dashboard/slices/dashboard-filter.slice.ts b/src/stores/dashboard/slices/dashboard-filter.slice.ts index 086ea959..dc398b78 100644 --- a/src/stores/dashboard/slices/dashboard-filter.slice.ts +++ b/src/stores/dashboard/slices/dashboard-filter.slice.ts @@ -25,8 +25,6 @@ export const createDashboardFilterSlice: StateCreator< setFilterValues: (values) => set({ filterValues: values }), resetFilterValues: () => { - alert('reset filter values'); - return set({ filterValues: { startDate: '', From 0f2173100866cf2e1cd5ee77764f7f49e5d66099 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 16:00:27 +0700 Subject: [PATCH 34/52] feat(FE): Add Lokasi and Kandang columns in RecordingTable --- .../pages/production/recording/RecordingTable.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index b854cd59..c30156ed 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -686,10 +686,18 @@ const RecordingTable = () => { 1, }, { - header: 'Nama Project', + header: 'Lokasi', + cell: (props) => props.row.original.location?.name || '-', + }, + { + header: 'Flock', cell: (props) => props.row.original.project_flock?.flock_name || '-', }, + { + header: 'Kandang', + cell: (props) => props.row.original.kandang?.name || '-', + }, { header: 'Periode', cell: (props) => props.row.original.project_flock?.period || '-', @@ -722,12 +730,10 @@ const RecordingTable = () => { }, }, { - accessorKey: 'warehouse.name', header: 'Gudang', cell: (props) => props.row.original.warehouse?.name, }, { - accessorKey: 'record_date', header: 'Waktu Recording', cell: (props) => formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), From 6e4c214821f1ea6ea0290dd31e4c8e27dd8c12f6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 17:21:48 +0700 Subject: [PATCH 35/52] refactor(FE): Remove Warehouse column from RecordingTable --- src/components/pages/production/recording/RecordingTable.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index c30156ed..7808c5bf 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -729,10 +729,6 @@ const RecordingTable = () => { ); }, }, - { - header: 'Gudang', - cell: (props) => props.row.original.warehouse?.name, - }, { header: 'Waktu Recording', cell: (props) => From f091b4be439785d8761d9c8edb3e8a43289c9d03 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 17:26:41 +0700 Subject: [PATCH 36/52] refactor(FE): Show kandangs table for add-request and selection --- .../pages/expense/form/ExpenseKandangsTable.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/pages/expense/form/ExpenseKandangsTable.tsx b/src/components/pages/expense/form/ExpenseKandangsTable.tsx index e4db88aa..5c60ae1e 100644 --- a/src/components/pages/expense/form/ExpenseKandangsTable.tsx +++ b/src/components/pages/expense/form/ExpenseKandangsTable.tsx @@ -174,9 +174,16 @@ const ExpenseKandangsTable = ({ updateSortingFilter('picSort', picSortFilter); }, [sorting, updateSortingFilter]); + // Tampilkan tabel jika: + // 1. Mode request pertama kali (type='add' dan formType='request') + // 2. Atau sudah ada kandang yang dipilih + const shouldShowTable = + (type === 'add' && formType === 'request') || + (selectedKandangs.length > 0 && selectedKandangs.some((k) => k.id)); + return ( <> - {selectedKandangs.length > 0 && selectedKandangs.some((k) => k.id) && ( + {shouldShowTable && ( Date: Mon, 19 Jan 2026 17:27:49 +0700 Subject: [PATCH 37/52] refactor(FE): Rename expense item note to notes --- src/components/pages/expense/ExpenseRealizationContent.tsx | 4 ++-- src/components/pages/expense/ExpenseRequestContent.tsx | 2 +- .../pages/expense/form/ExpenseRealizationForm.schema.ts | 4 ++-- .../pages/expense/form/ExpenseRequestForm.schema.ts | 2 +- src/components/pages/expense/pdf/ExpensePDF.tsx | 4 ++-- src/types/api/expense.d.ts | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/pages/expense/ExpenseRealizationContent.tsx b/src/components/pages/expense/ExpenseRealizationContent.tsx index abe00912..7057ba19 100644 --- a/src/components/pages/expense/ExpenseRealizationContent.tsx +++ b/src/components/pages/expense/ExpenseRealizationContent.tsx @@ -261,7 +261,7 @@ const ExpenseRealizationContent = ({ {pengajuanItem.qty} {formatCurrency(pengajuanItem.price)} - {pengajuanItem.note ?? '-'} + {pengajuanItem.notes ?? '-'} ) @@ -329,7 +329,7 @@ const ExpenseRealizationContent = ({ {realisasiItem.qty} {formatCurrency(realisasiItem.price)} - {realisasiItem.note ?? '-'} + {realisasiItem.notes ?? '-'} ) diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index ed39ea32..ac814bcf 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -654,7 +654,7 @@ const ExpenseRequestContent = ({ {pengajuanItem.qty} {formatCurrency(pengajuanItem.price)} - {pengajuanItem.note ?? '-'} + {pengajuanItem.notes ?? '-'} ) diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts b/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts index 037f5442..94d6e38d 100644 --- a/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRealizationForm.schema.ts @@ -159,7 +159,7 @@ export const getExpenseRealizationFormInitialValues = ( }, quantity: realisasiItem.qty, price: realisasiItem.price, - notes: realisasiItem.note, + notes: realisasiItem.notes, }; }) : kandangExpense.pengajuans @@ -170,7 +170,7 @@ export const getExpenseRealizationFormInitialValues = ( }, quantity: expenseItem.qty, price: expenseItem.price, - notes: expenseItem.note, + notes: expenseItem.notes, })) : []; diff --git a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts index 5d4a735b..6e0fb783 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts @@ -208,7 +208,7 @@ export const getExpenseFormInitialValues = ( nonstock_id: expenseItem.nonstock.id, quantity: expenseItem.qty, price: expenseItem.price, - notes: expenseItem.note, + notes: expenseItem.notes, })) : [], })) diff --git a/src/components/pages/expense/pdf/ExpensePDF.tsx b/src/components/pages/expense/pdf/ExpensePDF.tsx index f82f6639..f76b6f11 100644 --- a/src/components/pages/expense/pdf/ExpensePDF.tsx +++ b/src/components/pages/expense/pdf/ExpensePDF.tsx @@ -447,7 +447,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { ]} > - {pengajuan.note} + {pengajuan.notes} @@ -607,7 +607,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { ]} > - {realisasi.note} + {realisasi.notes} diff --git a/src/types/api/expense.d.ts b/src/types/api/expense.d.ts index 12455cc8..3ca57dd0 100644 --- a/src/types/api/expense.d.ts +++ b/src/types/api/expense.d.ts @@ -34,7 +34,7 @@ export type BaseExpense = { nonstock_id: number; qty: number; price: number; - note?: string; + notes?: string; nonstock: Pick; created_at: string; }[]; @@ -43,7 +43,7 @@ export type BaseExpense = { expense_nonstock_id: number; qty: number; price: number; - note?: string; + notes?: string; nonstock: Pick; created_at: string; }[]; From b1981867ffed095487c91efe80258de45382fe1e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 17:52:19 +0700 Subject: [PATCH 38/52] refactor(FE): Make depletion and egg fields optional --- .../recording/form/RecordingForm.schema.ts | 51 +++------ .../recording/form/RecordingForm.tsx | 104 ++++++------------ src/types/api/production/recording.d.ts | 10 +- 3 files changed, 56 insertions(+), 109 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 82b59036..063c2cc0 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -33,16 +33,16 @@ type RecordingGrowingFormSchemaType = { qty: number | string; }[]; depletions: { - product_warehouse_id: number; - qty: number | string; + product_warehouse_id?: number; + qty?: number | string; }[]; }; type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & { eggs: { - product_warehouse_id: number; - qty: number | string; - weight: number | string; + product_warehouse_id?: number; + qty?: number | string; + weight?: number | string; }[]; }; @@ -52,14 +52,14 @@ export type StockSchema = { }; export type DepletionSchema = { - product_warehouse_id: number; - qty: number | string; + product_warehouse_id?: number; + qty?: number | string; }; export type EggSchema = { - product_warehouse_id: number; - qty: number | string; - weight: number | string; + product_warehouse_id?: number; + qty?: number | string; + weight?: number | string; }; const StockObjectSchema: Yup.ObjectSchema = Yup.object({ @@ -75,28 +75,19 @@ const StockObjectSchema: Yup.ObjectSchema = Yup.object({ const DepletionObjectSchema: Yup.ObjectSchema = Yup.object({ product_warehouse_id: Yup.number() - .required('Produk depletions wajib diisi!') - .min(1, 'Produk depletions wajib diisi!') - .typeError('Produk depletions harus berupa angka!'), + .optional() + .typeError('Depletions harus berupa angka!'), qty: Yup.number() - .required('Jumlah depletions wajib diisi!') - .min(1, 'Jumlah depletions minimal 1!') + .optional() .typeError('Jumlah depletions harus berupa angka!'), }); const EggObjectSchema: Yup.ObjectSchema = Yup.object({ product_warehouse_id: Yup.number() - .required('Kondisi telur wajib diisi!') - .min(1, 'Kondisi telur wajib diisi!') + .optional() .typeError('Kondisi telur harus berupa angka!'), - qty: Yup.number() - .required('Jumlah telur wajib diisi!') - .min(1, 'Jumlah telur tidak boleh 0!') - .typeError('Jumlah telur harus berupa angka!'), - weight: Yup.number() - .required('Berat telur wajib diisi!') - .min(1, 'Berat telur minimal 1 gram!') - .typeError('Berat telur harus berupa angka!'), + qty: Yup.number().optional().typeError('Jumlah telur harus berupa angka!'), + weight: Yup.number().optional().typeError('Berat telur harus berupa angka!'), }); export const RecordingGrowingFormSchema: Yup.ObjectSchema = @@ -163,18 +154,12 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema = RecordingGrowingFormSchema.shape({ - eggs: Yup.array() - .of(EggObjectSchema) - .min(1, 'Minimal harus ada 1 data telur!') - .required('Data telur wajib diisi!'), + eggs: Yup.array().of(EggObjectSchema).default([]), }); export const UpdateRecordingGrowingFormSchema = diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 5044dec5..17a1d020 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -309,6 +309,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== PAYLOAD CREATION HELPERS ===== const createGrowingPayload = useCallback( (values: RecordingGrowingFormValues) => { + const depletions = values.depletions + ?.filter((d) => d.product_warehouse_id && d.qty) + .map((depletion) => ({ + product_warehouse_id: depletion.product_warehouse_id!, + qty: Number(depletion.qty) || 0, + })); + return { project_flock_kandang_id: values.project_flock_kandang_id, record_date: values.record_date, @@ -316,10 +323,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, })), - depletions: (values.depletions ?? []).map((depletion) => ({ - product_warehouse_id: depletion.product_warehouse_id, - qty: Number(depletion.qty) || 0, - })), + ...(depletions && depletions.length > 0 && { depletions }), }; }, [] @@ -327,25 +331,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const createLayingPayload = useCallback( (values: RecordingLayingFormValues) => { - return { - project_flock_kandang_id: values.project_flock_kandang_id, - record_date: values.record_date, - stocks: (values.stocks ?? []).map((stock) => ({ - product_warehouse_id: stock.product_warehouse_id, - qty: Number(stock.qty) || 0, - })), - depletions: (values.depletions ?? []).map((depletion) => ({ - product_warehouse_id: depletion.product_warehouse_id, + const depletions = values.depletions + ?.filter((d) => d.product_warehouse_id && d.qty) + .map((depletion) => ({ + product_warehouse_id: depletion.product_warehouse_id!, qty: Number(depletion.qty) || 0, - })), - eggs: (values.eggs ?? []).map((egg) => ({ - product_warehouse_id: egg.product_warehouse_id, + })); + + const eggs = values.eggs + ?.filter((e) => e.product_warehouse_id && e.qty && e.weight) + .map((egg) => ({ + product_warehouse_id: egg.product_warehouse_id!, qty: Number(egg.qty) || 0, weight: typeof egg.weight === 'number' ? egg.weight : parseFloat(String(egg.weight)) || 0, + })); + + return { + project_flock_kandang_id: values.project_flock_kandang_id, + record_date: values.record_date, + stocks: values.stocks.map((stock) => ({ + product_warehouse_id: stock.product_warehouse_id, + qty: Number(stock.qty) || 0, })), + ...(depletions && depletions.length > 0 && { depletions }), + ...(eggs && eggs.length > 0 && { eggs }), }; }, [] @@ -1692,12 +1704,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )} -
- {JSON.stringify(formik.errors)} -
-
- {JSON.stringify(formik.values)} -
{/* Basic Info Card */} {(type === 'add' || type === 'edit') && ( { /> )} - - Kondisi - - * - - - - Jumlah - - * - - + Kondisi + Jumlah {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( Action )} @@ -2652,7 +2642,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { /> )} - - Kondisi Telur - - * - - - - Jumlah - - * - - - - Berat (gram) - - * - - + Kondisi Telur + Jumlah + Berat (gram) {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( Action )} @@ -2829,7 +2794,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )} @@ -2872,7 +2836,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { { Date: Mon, 19 Jan 2026 20:52:37 +0700 Subject: [PATCH 39/52] refactor(FE): Use ProjectFlockKandangApi for kandang select --- .../pages/report/sale/tab/HppPerKandangTab.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index eb66e0c4..ab52a0da 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -10,7 +10,7 @@ import DateInput from '@/components/input/DateInput'; import NumberInput from '@/components/input/NumberInput'; import { AreaApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data'; -import { KandangApi } from '@/services/api/master-data'; +import { ProjectFlockKandangApi } from '@/services/api/production'; import { SaleReportApi } from '@/services/api/report/marketing-sale'; import Table from '@/components/Table'; import { ColumnDef, Row, flexRender } from '@tanstack/react-table'; @@ -80,7 +80,12 @@ const HppPerKandangTab = () => { options: kandangOptions, isLoadingOptions: isLoadingKandangs, loadMore: loadMoreKandangs, - } = useSelect(KandangApi.basePath, 'id', 'name', 'search'); + } = useSelect( + ProjectFlockKandangApi.basePath, + 'id', + 'name_with_period', + 'search' + ); const showUnrecordedOptions: OptionType[] = [ { value: 'false', label: 'Sembunyikan' }, From cbc54eb501246409987ca9cf6c1ae75a1694c331 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 20:57:16 +0700 Subject: [PATCH 40/52] refactor(FE): Use name_with_periode in HppPerKandang cell --- src/components/pages/report/sale/tab/HppPerKandangTab.tsx | 4 ++-- src/types/api/production/project-flock-kandang.d.ts | 1 + src/types/api/report/hpp-per-kandang.d.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index ab52a0da..7bd774f3 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -562,8 +562,8 @@ const HppPerKandangTab = () => { header: 'Kandang', accessorKey: 'kandang.name', cell: (props) => { - const kandang = props.row.original.kandang; - return kandang?.name || '-'; + const row = props.row.original; + return row.name_with_periode || row.kandang?.name || '-'; }, footer: () =>
ALL
, }, diff --git a/src/types/api/production/project-flock-kandang.d.ts b/src/types/api/production/project-flock-kandang.d.ts index 8c8d6273..111ca98b 100644 --- a/src/types/api/production/project-flock-kandang.d.ts +++ b/src/types/api/production/project-flock-kandang.d.ts @@ -10,6 +10,7 @@ export type BaseProjectFlockKandang = { kandang_id: number; kandang: Kandang; project_flock: ProjectFlock; + name_with_period?: string; approval: BaseApproval; chickins?: Chickin[]; available_qtys?: AvailableQty[]; diff --git a/src/types/api/report/hpp-per-kandang.d.ts b/src/types/api/report/hpp-per-kandang.d.ts index 208a2cdb..0d47fc3f 100644 --- a/src/types/api/report/hpp-per-kandang.d.ts +++ b/src/types/api/report/hpp-per-kandang.d.ts @@ -5,6 +5,7 @@ import { Kandang } from '@/types/api/master-data/kandang'; export type HppPerKandangRow = { id: number; kandang: Kandang; + name_with_periode?: string; weight_range: { weight_min: number; weight_max: number; From 6319b6d5fe397b0618dd76c7b5bf820f7b417870 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 20 Jan 2026 10:07:39 +0700 Subject: [PATCH 41/52] refactor(FE): Guard warehouse before accessing location --- src/components/pages/purchase/order/PurchaseOrderDetail.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index 72f9c474..61044c97 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -723,8 +723,8 @@ const PurchaseOrderDetail = ({ :{' '} - {purchaseData.items?.[0]?.warehouse?.type === 'LOKASI' && - purchaseData.items?.[0]?.warehouse?.location?.name + {purchaseData.items?.[0]?.warehouse && + 'location' in purchaseData.items[0].warehouse ? purchaseData.items[0].warehouse.location.name : '-'} From 0e179f1643b22d3d59d1d96ef00c9bd41c5e9801 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 20 Jan 2026 10:08:37 +0700 Subject: [PATCH 42/52] refactor(FE): Use location type guard for warehouse access --- .../pages/purchase/order/PurchaseOrderInvoice.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx b/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx index 36aea9c7..aed154d0 100644 --- a/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx @@ -324,12 +324,14 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => { PT LUMBUNG TELUR INDONESIA - {purchaseData?.items?.[0]?.warehouse.type === 'LOKASI' + {purchaseData?.items?.[0]?.warehouse && + 'location' in purchaseData.items[0].warehouse ? purchaseData.items[0].warehouse.location.name : '-'} - {purchaseData?.items?.[0]?.warehouse.type === 'LOKASI' + {purchaseData?.items?.[0]?.warehouse && + 'location' in purchaseData.items[0].warehouse ? purchaseData.items[0].warehouse.location.address : '-'} @@ -434,7 +436,7 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => { - {item.warehouse?.type === 'LOKASI' + {item.warehouse && 'location' in item.warehouse ? item.warehouse.location.address : '-'} From 28a32cb6c48f4c927057e406f3414d4a4978b1dd Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 20 Jan 2026 10:21:22 +0700 Subject: [PATCH 43/52] refactor(FE): Move travel document button into receipt section --- .../purchase/order/PurchaseOrderDetail.tsx | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index 61044c97..21307af9 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -540,31 +540,6 @@ const PurchaseOrderDetail = ({ accessorKey: 'travel_number', cell: (props) => props.row.original.travel_number || '-', }, - { - header: 'Dokumen Surat Jalan', - accessorKey: 'travel_document_path', - cell: (props) => { - const documentPath = props.row.original.travel_document_path; - return documentPath ? ( - - ) : ( - '-' - ); - }, - }, { header: 'No. Armada Pengangkut', accessorKey: 'vehicle_number', @@ -905,11 +880,29 @@ const PurchaseOrderDetail = ({ Informasi Penerimaan Barang {canShowPenerimaanBarang && ( - - - +
+ {goodsReceiptItems[0]?.travel_document_path && ( + + )} + + + +
)}
From 72356917ff8f15d51e398086d3859ff1bdb3f0bf Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 20 Jan 2026 10:23:40 +0700 Subject: [PATCH 44/52] refactor(FE): Show dash for missing transport per item --- src/components/pages/purchase/order/PurchaseOrderDetail.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index 21307af9..9f010f03 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -563,7 +563,10 @@ const PurchaseOrderDetail = ({ { header: 'Transport /Item', accessorKey: 'transport_per_item', - cell: (props) => formatCurrency(props.getValue() as number), + cell: (props) => { + const value = props.row.original.transport_per_item; + return value ? formatCurrency(value) : '-'; + }, }, ]; From 46b819c200b2353db00c3ce1673e3d9f25dccd87 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 20 Jan 2026 10:27:16 +0700 Subject: [PATCH 45/52] refactor(FE): Use approval step_name or action for status --- .../production/recording/RecordingTable.tsx | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 7808c5bf..8d8caad1 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -1013,21 +1013,6 @@ const RecordingTable = () => { approvalHistoryModal.openModal(); }; - const getStatusText = (action: string) => { - switch (action) { - case 'APPROVED': - return 'Disetujui'; - case 'REJECTED': - return 'Ditolak'; - case 'CREATED': - return 'Dibuat'; - case 'UPDATED': - return 'Diperbarui'; - default: - return action; - } - }; - return ( { }} onClick={openApprovalHistory} > - {getStatusText(approval.action)} + {approval.step_name || approval.action} ); }, From 8b0a6f054b52e5c45d130d7a322e9ca686b4639c Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 20 Jan 2026 10:31:32 +0700 Subject: [PATCH 46/52] refactor(FE): Format zero transport per item as currency --- src/components/pages/purchase/order/PurchaseOrderDetail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index 9f010f03..47dbc8f0 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -565,7 +565,7 @@ const PurchaseOrderDetail = ({ accessorKey: 'transport_per_item', cell: (props) => { const value = props.row.original.transport_per_item; - return value ? formatCurrency(value) : '-'; + return value ? formatCurrency(value) : formatCurrency(0); }, }, ]; From bbb9c5f19036e1fecb645414d8d4063680eff257 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 20 Jan 2026 10:43:13 +0700 Subject: [PATCH 47/52] feat(FE): adding edit and delete per product row sales order --- .../pages/marketing/form/MarketingForm.tsx | 35 +++++++++++++------ .../sales-order/SalesOrderProduct.schema.ts | 2 ++ .../sales-order/SalesOrderProductForm.tsx | 31 +++++++++++----- .../table-view/SalesOrderProductTable.tsx | 25 +++++++++++-- 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/components/pages/marketing/form/MarketingForm.tsx b/src/components/pages/marketing/form/MarketingForm.tsx index be4367cb..2d28613a 100644 --- a/src/components/pages/marketing/form/MarketingForm.tsx +++ b/src/components/pages/marketing/form/MarketingForm.tsx @@ -361,6 +361,8 @@ const MarketingForm = ({ }, }); + const memoSalesOrder = formik.values.sales_order; + // ================== FORM REPEATER HANDLER ================== const createMarketingHandler = async (values: CreateSalesOrderPayload) => { setIsLoading(true); @@ -471,13 +473,25 @@ const MarketingForm = ({ }, [deleteModal]); // ================== SALES ORDER HANDLER ================== - const handleDeleteSO = useCallback((id: number) => { - const currentProducts = formik.values.sales_order; - formik.setFieldValue( - 'sales_order', - currentProducts.filter((p) => p.id != id) - ); - }, []); + const handleDeleteSO = useCallback( + (id: number) => { + const currentProducts = formik.values.sales_order; + formik.setFieldValue( + 'sales_order', + currentProducts.filter((p) => p.id != id) + ); + }, + [memoSalesOrder] + ); + const handleEditSO = useCallback( + (id: number) => { + const currentProducts = formik.values.sales_order; + const selectedProduct = currentProducts.find((p) => p.id == id); + setSelectedMarketingProduct(selectedProduct ?? null); + addSOModal.openModal(); + }, + [memoSalesOrder] + ); const handleBulkDeleteSO = useCallback(() => { const currentProducts = formik.values.sales_order; formik.setFieldValue( @@ -487,7 +501,7 @@ const MarketingForm = ({ ) ); setRowSOSelection({}); - }, [selectedRowSOIds]); + }, [selectedRowSOIds, memoSalesOrder]); const handleAddSOClick = useCallback(() => { setSelectedMarketingProduct(null); addSOModal.openModal(); @@ -523,7 +537,7 @@ const MarketingForm = ({ addSOModal.closeModal(); }, - [addSOModal] + [addSOModal, memoSalesOrder] ); // ================== DELIVERY ORDER HANDLER ================== @@ -569,8 +583,6 @@ const MarketingForm = ({ [addDOModal] ); - const memoSalesOrder = formik.values.sales_order; - useEffect(() => { formik.setFieldValue('delivery_order', deliveryOrderValues); }, [deliveryOrderValues, initialValues]); @@ -652,6 +664,7 @@ const MarketingForm = ({ setRowSelection={setRowSOSelection} selectedRowIds={selectedRowSOIds} onDelete={handleDeleteSO} + onEdit={handleEditSO} onBulkDelete={handleBulkDeleteSO} onAddProductClick={handleAddSOClick} /> diff --git a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts index e62ed701..879efc12 100644 --- a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts +++ b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts @@ -18,6 +18,7 @@ type SalesOrderProductSchemaType = { avg_weight: string | number | undefined; total_price: string | number | undefined; vehicle_number?: string | undefined; + uom?: string | null | undefined; }; export const SalesOrderProductSchema: Yup.ObjectSchema = @@ -57,6 +58,7 @@ export const SalesOrderProductSchema: Yup.ObjectSchema({ enableReinitialize: true, initialValues: { - vehicle_number: initialValues?.vehicle_number || undefined, + vehicle_number: initialValues?.vehicle_number || '', kandang_id: initialValues?.kandang_id || undefined, - kandang: initialValues?.kandang || undefined, - product_warehouse: initialValues?.product_warehouse || undefined, + kandang: initialValues?.kandang || null, + product_warehouse: initialValues?.product_warehouse || null, product_warehouse_id: initialValues?.product_warehouse_id || undefined, - unit_price: initialValues?.unit_price || undefined, - total_weight: initialValues?.total_weight || undefined, - qty: initialValues?.qty || undefined, - avg_weight: initialValues?.avg_weight || undefined, - total_price: initialValues?.total_price || undefined, + unit_price: initialValues?.unit_price || '', + total_weight: initialValues?.total_weight || '', + qty: initialValues?.qty || '', + avg_weight: initialValues?.avg_weight || '', + total_price: initialValues?.total_price || '', + uom: initialValues?.uom || '', }, validationSchema: SalesOrderProductSchema, onSubmit: async (values) => { @@ -220,7 +221,19 @@ const SalesOrderProductForm = ({ }; // ===== Formik Error List ===== - const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + const { formErrorList, close, handleFormSubmit } = useFormikErrorList( + formik, + { + onBeforeSubmit(e) { + e.preventDefault(); + handleBlurField(currentInput); + formik.setFieldValue( + 'uom', + isResponseSuccess(productData) ? productData?.data?.uom.name : '' + ); + }, + } + ); return ( <> diff --git a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx index c37bbfb1..81ed489b 100644 --- a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx +++ b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx @@ -23,6 +23,7 @@ type SalesOrderProductTableProps = { >; selectedRowIds: number[]; onDelete: (id: number) => void; + onEdit: (id: number) => void; onBulkDelete: () => void; onAddProductClick: () => void; }; @@ -34,11 +35,14 @@ const SalesOrderProductTable = ({ setRowSelection, selectedRowIds, onDelete, + onEdit, onBulkDelete, onAddProductClick, }: SalesOrderProductTableProps) => { const onDeleteRef = useRef(onDelete); onDeleteRef.current = onDelete; + const onEditRef = useRef(onEdit); + onEditRef.current = onEdit; const columns = useMemo( () => [ @@ -92,17 +96,26 @@ const SalesOrderProductTable = ({ }, { accessorFn: (row: SalesOrderProductFormValues) => - formatNumber(parseFloat(row.total_weight as string)), + formatNumber(parseFloat(row.total_weight as string), undefined, 0, 5), header: 'Total Bobot (Kg)', }, { accessorFn: (row: SalesOrderProductFormValues) => formatNumber(parseFloat(row.qty as string)), header: 'Kuantitas', + cell: ({ row }: { row: TanStack.Row }) => + formatNumber( + parseFloat(row.original.qty as string), + undefined, + 0, + 5 + ) + + ' ' + + row.original.uom, }, { accessorFn: (row: SalesOrderProductFormValues) => - formatNumber(parseFloat(row.avg_weight as string)), + formatNumber(parseFloat(row.avg_weight as string), undefined, 0, 5), header: 'Avg. Bobot (Kg)', }, { @@ -116,6 +129,14 @@ const SalesOrderProductTable = ({ props: TanStack.CellContext ) => (
+ + <> + + + )} {!props.row.original.qty && '-'} diff --git a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx index 81ed489b..02d33587 100644 --- a/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx +++ b/src/components/pages/marketing/form/table-view/SalesOrderProductTable.tsx @@ -111,7 +111,7 @@ const SalesOrderProductTable = ({ 5 ) + ' ' + - row.original.uom, + (row.original.uom ?? ''), }, { accessorFn: (row: SalesOrderProductFormValues) => @@ -135,7 +135,7 @@ const SalesOrderProductTable = ({ onClick={() => onEditRef.current(props.row.original.id as number)} type='button' > - + Edit
), From fc785bc63c7ae9a235bac861ceea854dfd6d334c Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 20 Jan 2026 14:39:44 +0700 Subject: [PATCH 49/52] fix(FE): fixing unit production standard --- .../form/ProductionStandardForm.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx index 4512f474..c445a840 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx @@ -367,7 +367,7 @@ const ProductionStandardForm = ({ accessorFn: (row) => row.production_standard_details?.target_hen_house_production, cell: ({ row }) => - `${row.original.production_standard_details?.target_hen_house_production} pc`, + `${row.original.production_standard_details?.target_hen_house_production} btr`, enableSorting: false, }, { @@ -383,7 +383,7 @@ const ProductionStandardForm = ({ accessorFn: (row) => row.production_standard_details?.target_egg_mass, cell: ({ row }) => - `${row.original.production_standard_details?.target_egg_mass} g`, + `${row.original.production_standard_details?.target_egg_mass} kg`, enableSorting: false, }, { @@ -958,7 +958,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - bottomLabel='Butir (pc)' + bottomLabel='Butir (btr)' errorMessage={getProductionDetailsError( repeaterFormik.errors .production_standard_details, @@ -1015,7 +1015,7 @@ const ProductionStandardForm = ({ name='production_standard_details.target_egg_mass' label='Egg Mass' placeholder='1' - bottomLabel='Gram (g)' + bottomLabel='Kg (kg)' value={ repeaterFormik.values .production_standard_details?.target_egg_mass @@ -1176,7 +1176,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - bottomLabel='Gram/Ekor (g)' + bottomLabel='Gram (g)' endAdornment errorMessage={ repeaterFormik.errors From 4cb8343f74c694140c6a4b888f7bd1a2f7908d28 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 20 Jan 2026 15:00:50 +0700 Subject: [PATCH 50/52] fix(FE): fixing edit product SO per row --- .../pages/marketing/detail/MarketingDetail.tsx | 1 - .../pages/marketing/form/MarketingForm.tsx | 14 ++++---------- .../repeater/sales-order/SalesOrderProductForm.tsx | 11 ++++++++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/pages/marketing/detail/MarketingDetail.tsx b/src/components/pages/marketing/detail/MarketingDetail.tsx index 91e02928..f8f7d269 100644 --- a/src/components/pages/marketing/detail/MarketingDetail.tsx +++ b/src/components/pages/marketing/detail/MarketingDetail.tsx @@ -89,7 +89,6 @@ const MarketingDetail = ({ deleteModal.closeModal(); router.push('/marketing'); toast.success(res?.message as string); - refresh?.(); setIsLoading(false); }; diff --git a/src/components/pages/marketing/form/MarketingForm.tsx b/src/components/pages/marketing/form/MarketingForm.tsx index b9a1a1fb..ac20c0c9 100644 --- a/src/components/pages/marketing/form/MarketingForm.tsx +++ b/src/components/pages/marketing/form/MarketingForm.tsx @@ -507,7 +507,7 @@ const MarketingForm = ({ addSOModal.openModal(); }, [addSOModal]); const handleAddSubmitSO = useCallback( - async (values: SalesOrderProductFormValues) => { + async (values: SalesOrderProductFormValues, id?: number) => { const currentProducts = formik.values.sales_order; const newValues = { @@ -515,18 +515,12 @@ const MarketingForm = ({ id: values.id ?? Date.now(), }; - const existingIndex = currentProducts.findIndex( - (item) => - item.kandang_id === newValues.kandang_id && - item.product_warehouse_id === newValues.product_warehouse_id - ); - let updatedProducts = []; - if (existingIndex !== -1) { + if (id) { // Overwrite - updatedProducts = currentProducts.map((item, index) => - index === existingIndex ? newValues : item + updatedProducts = currentProducts.map((item) => + item.id === id ? newValues : item ); } else { // Add new item diff --git a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx index 52d844cd..8bea75c4 100644 --- a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx +++ b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx @@ -39,7 +39,10 @@ const SalesOrderProductForm = ({ initialValues?: SalesOrderProductFormValues; exisitingValues?: SalesOrderProductFormValues[]; modalRef?: RefObject; - onSubmitForm?: (value: SalesOrderProductFormValues) => Promise; + onSubmitForm?: ( + value: SalesOrderProductFormValues, + id?: number + ) => Promise; }) => { const [formErrorMessage, setFormErrorMessage] = useState(''); const [currentInput, setCurrentInput] = useState(''); @@ -76,7 +79,7 @@ const SalesOrderProductForm = ({ validationSchema: SalesOrderProductSchema, onSubmit: async (values) => { setFormErrorMessage(''); - onSubmitForm?.(values); + onSubmitForm?.(values, initialValues?.id); handleResetForm(); }, validateOnBlur: true, @@ -414,7 +417,9 @@ const SalesOrderProductForm = ({ />
- +
+ +
- - )} + ) && ( + + + + )} {FINANCE_INITIAL_BALANCE_STATUS.includes( props.row.original.transaction_type @@ -148,7 +144,8 @@ const FinanceTable = () => { search: '', transactionType: '', bankId: '', - partyType: '', + customerId: '', + supplierId: '', sortBy: '', startDate: '', endDate: '', @@ -158,7 +155,8 @@ const FinanceTable = () => { pageSize: 'limit', transactionType: 'transaction_type', bankId: 'bank_id', - partyType: 'party_type', + customerId: 'customer_id', + supplierId: 'supplier_id', sortBy: 'sort_date', startDate: 'start_date', endDate: 'end_date', @@ -172,7 +170,8 @@ const FinanceTable = () => { search: '', transactionType: '', bankId: '', - partyType: '', + customerId: '', + supplierId: '', sortBy: '', startDate: '', endDate: '', @@ -180,9 +179,10 @@ const FinanceTable = () => { const [selectedTransactionType, setSelectedTransactionType] = useState(null); const [selectedBank, setSelectedBank] = useState(null); - const [selectedPartyType, setSelectedPartyType] = useState( - null - ); + const [selectedCustomerId, setSelectedCustomerId] = + useState(null); + const [selectedSupplierId, setSelectedSupplierId] = + useState(null); const [selectedSortBy, setSelectedSortBy] = useState(null); const [selectedFinance, setSelectedFinance] = useState(null); const [isDeleteLoading, setIsDeleteLoading] = useState(false); @@ -197,27 +197,18 @@ const FinanceTable = () => { FinanceApi.getAllFetcher ); - // ===== Options ===== - const transactionTypeOptions = useMemo(() => { - return [ - { label: 'Customer', value: 'CUSTOMER' }, - { label: 'Supplier', value: 'SUPPLIER' }, - ]; - }, []); const { - options: partyTypeOptions, - isLoadingOptions: partyTypeIsLoadingOptions, - setInputValue: partyTypeInputValue, - loadMore: partyTypeLoadMore, - } = useSelect( - selectedTransactionType - ? selectedTransactionType.value === 'CUSTOMER' - ? CustomerApi.basePath - : SupplierApi.basePath - : '', - 'id', - 'name' - ); + options: customerOptions, + isLoadingOptions: customerIsLoadingOptions, + setInputValue: customerInputValue, + loadMore: customerLoadMore, + } = useSelect(CustomerApi.basePath, 'id', 'name'); + const { + options: supplierOptions, + isLoadingOptions: supplierIsLoadingOptions, + setInputValue: supplierInputValue, + loadMore: supplierLoadMore, + } = useSelect(SupplierApi.basePath, 'id', 'name'); const sortByOptions = useMemo(() => { return [ { label: 'Tanggal Pembayaran', value: 'payment_date' }, @@ -251,11 +242,18 @@ const FinanceTable = () => { bankId: val ? ((val as OptionType).value as string) : '', })); }; - const partyTypeChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedPartyType(val as OptionType); + const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedCustomerId(val as OptionType); setPendingFilters((prev) => ({ ...prev, - partyType: val ? ((val as OptionType).value as string) : '', + customerId: val ? ((val as OptionType).value as string) : '', + })); + }; + const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedSupplierId(val as OptionType); + setPendingFilters((prev) => ({ + ...prev, + supplierId: val ? ((val as OptionType).value as string) : '', })); }; const sortByChangeHandler = (val: OptionType | OptionType[] | null) => { @@ -279,7 +277,8 @@ const FinanceTable = () => { updateFilter('search', pendingFilters.search); updateFilter('transactionType', pendingFilters.transactionType); updateFilter('bankId', pendingFilters.bankId); - updateFilter('partyType', pendingFilters.partyType); + updateFilter('customerId', pendingFilters.customerId); + updateFilter('supplierId', pendingFilters.supplierId); updateFilter('sortBy', pendingFilters.sortBy); updateFilter('startDate', pendingFilters.startDate); updateFilter('endDate', pendingFilters.endDate); @@ -287,14 +286,16 @@ const FinanceTable = () => { const resetFilterHandler = () => { setSelectedTransactionType(null); setSelectedBank(null); - setSelectedPartyType(null); + setSelectedCustomerId(null); + setSelectedSupplierId(null); setSelectedSortBy(null); const emptyFilters = { search: '', transactionType: '', bankId: '', - partyType: '', + customerId: '', + supplierId: '', sortBy: '', startDate: '', endDate: '', @@ -304,7 +305,8 @@ const FinanceTable = () => { updateFilter('search', ''); updateFilter('transactionType', ''); updateFilter('bankId', ''); - updateFilter('partyType', ''); + updateFilter('customerId', ''); + updateFilter('supplierId', ''); updateFilter('sortBy', ''); updateFilter('startDate', ''); updateFilter('endDate', ''); @@ -477,26 +479,30 @@ const FinanceTable = () => { >
+ { onMenuScrollToBottom={bankLoadMore} isClearable /> - { value={pendingFilters.endDate} onChange={endDateChangeHandler} /> +
diff --git a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx index e1a31415..cb46c0e1 100644 --- a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx +++ b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx @@ -245,7 +245,11 @@ const FormFinanceAddInitialBalance = ({ } required isClearable - isDisabled={!formik.values.party_type_option?.value} + isDisabled={ + !formik.values.party_type_option?.value || + (type === 'edit' && + formik.values.party_type_option?.value == 'SUPPLIER') + } /> Date: Tue, 20 Jan 2026 15:34:33 +0700 Subject: [PATCH 52/52] fix(FE): fixing url encode for filters --- src/components/pages/finance/FinanceTable.tsx | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index 686a6d4e..505e411f 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -176,13 +176,18 @@ const FinanceTable = () => { startDate: '', endDate: '', }); - const [selectedTransactionType, setSelectedTransactionType] = - useState(null); - const [selectedBank, setSelectedBank] = useState(null); - const [selectedCustomerId, setSelectedCustomerId] = - useState(null); - const [selectedSupplierId, setSelectedSupplierId] = - useState(null); + const [selectedTransactionType, setSelectedTransactionType] = useState< + OptionType | OptionType[] | null + >(null); + const [selectedBank, setSelectedBank] = useState< + OptionType | OptionType[] | null + >(null); + const [selectedCustomerId, setSelectedCustomerId] = useState< + OptionType | OptionType[] | null + >(null); + const [selectedSupplierId, setSelectedSupplierId] = useState< + OptionType | OptionType[] | null + >(null); const [selectedSortBy, setSelectedSortBy] = useState(null); const [selectedFinance, setSelectedFinance] = useState(null); const [isDeleteLoading, setIsDeleteLoading] = useState(false); @@ -229,31 +234,47 @@ const FinanceTable = () => { const transactionTypeChangeHandler = ( val: OptionType | OptionType[] | null ) => { - setSelectedTransactionType(val as OptionType); + setSelectedTransactionType(val); setPendingFilters((prev) => ({ ...prev, - transactionType: val ? ((val as OptionType).value as string) : '', + transactionType: val + ? Array.isArray(val) + ? val.map((item) => item.value).join(',') + : (val.value as string) + : '', })); }; const bankChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedBank(val as OptionType); + setSelectedBank(val); setPendingFilters((prev) => ({ ...prev, - bankId: val ? ((val as OptionType).value as string) : '', + bankId: val + ? Array.isArray(val) + ? val.map((item) => item.value).join(',') + : (val.value as string) + : '', })); }; const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedCustomerId(val as OptionType); + setSelectedCustomerId(val); setPendingFilters((prev) => ({ ...prev, - customerId: val ? ((val as OptionType).value as string) : '', + customerId: val + ? Array.isArray(val) + ? val.map((item) => item.value).join(',') + : (val.value as string) + : '', })); }; const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedSupplierId(val as OptionType); + setSelectedSupplierId(val); setPendingFilters((prev) => ({ ...prev, - supplierId: val ? ((val as OptionType).value as string) : '', + supplierId: val + ? Array.isArray(val) + ? val.map((item) => item.value).join(',') + : (val.value as string) + : '', })); }; const sortByChangeHandler = (val: OptionType | OptionType[] | null) => { @@ -484,6 +505,7 @@ const FinanceTable = () => { value={selectedTransactionType} onChange={transactionTypeChangeHandler} isClearable + isMulti /> { onMenuScrollToBottom={customerLoadMore} isLoading={customerIsLoadingOptions} isClearable + isMulti /> { onMenuScrollToBottom={supplierLoadMore} isLoading={supplierIsLoadingOptions} isClearable + isMulti /> { onInputChange={bankInputValue} onMenuScrollToBottom={bankLoadMore} isClearable + isMulti />