From a82c5e55934a5b4aea4fa2a8a8ecc3152fe4d296 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 16:16:34 +0700 Subject: [PATCH 01/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 eefec938113f1c687227b9fcb87f3df60a0ff67e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 14:59:26 +0700 Subject: [PATCH 26/28] 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 27/28] 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 0f2173100866cf2e1cd5ee77764f7f49e5d66099 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 19 Jan 2026 16:00:27 +0700 Subject: [PATCH 28/28] 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'),