From a82860cb68c4e4a746f9f126e39dec72e60e42bc Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 10:34:53 +0700 Subject: [PATCH 01/23] refactor(FE): Add initial balance row and normalize empty values --- .../export/CustomerPaymentExportPDF.tsx | 230 +++++++++++++----- .../export/CustomerPaymentExportXLSX.tsx | 52 +++- .../report/finance/tab/CustomerPaymentTab.tsx | 12 +- 3 files changed, 216 insertions(+), 78 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index aa04b4f0..77e33866 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -320,110 +320,203 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Pengambilan - + Sales {/* Table Body */} - {customerReport.rows.map((item, index) => ( - + <> + {/* Initial Balance Row */} + - {index + 1} + - - {item.trans_date - ? formatDate(item.trans_date, 'DD MMM YY') - : '-'} - + - - {item.delivery_date - ? formatDate(item.delivery_date, 'DD MMM YY') - : '-'} - + - - {item.aging_day ? formatNumber(item.aging_day) : '-'} hari - + - {item.reference || '-'} + - - {Array.isArray(item.vehicle_numbers) - ? item.vehicle_numbers.join(', ') - : item.vehicle_numbers || '-'} - + - {formatNumber(item.qty)} + - {formatNumber(item.weight)} + - {formatNumber(item.average_weight)} + - {formatCurrency(item.unit_price)} + - {formatCurrency(item.final_price)} + - {formatCurrency(item.total_price)} + - {formatCurrency(item.payment_amount)} + - - - {formatCurrency(item.accounts_receivable)} + + + {formatCurrency(customerReport.initial_balance || 0)} - {item.status ? ( - - - {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'} - - - ) : ( - - - )} + - - {Array.isArray(item.pickup_info) - ? item.pickup_info.join(', ') - : item.pickup_info || '-'} - + - - {item.sales_person || '-'} + + - ))} + + {/* Data Rows */} + {customerReport.rows.map((item, index) => ( + + + {index + 1} + + + + {item.trans_date + ? formatDate(item.trans_date, 'DD MMM YY') + : '-'} + + + + + {item.delivery_date + ? formatDate(item.delivery_date, 'DD MMM YY') + : '-'} + + + + + {item.aging_day != null + ? `${formatNumber(item.aging_day)} hari` + : '-'} + + + + {item.reference || '-'} + + + + {Array.isArray(item.vehicle_numbers) + ? item.vehicle_numbers.length > 0 + ? item.vehicle_numbers.join(', ') + : '-' + : '-'} + + + + {formatNumber(item.qty)} + + + {formatNumber(item.weight)} + + + {formatNumber(item.average_weight)} + + + {formatCurrency(item.unit_price)} + + + {formatCurrency(item.final_price)} + + + {formatCurrency(item.total_price)} + + + {formatCurrency(item.payment_amount)} + + + + {formatCurrency(item.accounts_receivable)} + + + + {item.status ? ( + + + {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'} + + + ) : ( + - + )} + + + + {Array.isArray(item.pickup_info) + ? item.pickup_info.length > 0 + ? item.pickup_info.join(', ') + : '-' + : '-'} + + + + {item.sales_person || '-'} + + + ))} + {/* Summary Row */} {customerReport.summary && ( @@ -488,7 +581,12 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - + diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index fec4dc6b..3238d46e 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -44,20 +44,50 @@ export const generateCustomerPaymentExcel = async ( const worksheet = workbook.addWorksheet(customerName.substring(0, 31)); worksheet.columns = columns; + const initialRow = worksheet.addRow({ + no: '', + transDate: '', + deliveryDate: '', + aging: '', + reference: '', + vehicleNumbers: '', + qty: '', + weight: '', + avgWeight: '', + unitPrice: '', + finalPrice: '', + totalPrice: '', + paymentAmount: '', + accountsReceivable: formatCurrency(customerReport.initial_balance || 0), + status: '', + pickupInfo: '', + salesPerson: '', + }); + + const initialBalanceCell = initialRow.getCell('accountsReceivable'); + if ( + typeof customerReport.initial_balance === 'number' && + customerReport.initial_balance < 0 + ) { + initialBalanceCell.font = { color: { argb: 'FFFF0000' } }; + } + customerData.forEach((item, index) => { const row = worksheet.addRow({ no: index + 1, transDate: item.trans_date ? formatDate(item.trans_date, 'DD MMM YYYY') - : '', + : '-', deliveryDate: item.delivery_date ? formatDate(item.delivery_date, 'DD MMM YYYY') - : '', - aging: formatNumber(item.aging_day || 0), - reference: item.reference || '', + : '-', + aging: item.aging_day != null ? formatNumber(item.aging_day) : '-', + reference: item.reference || '-', vehicleNumbers: Array.isArray(item.vehicle_numbers) - ? item.vehicle_numbers.join(', ') - : '', + ? item.vehicle_numbers.length > 0 + ? item.vehicle_numbers.join(', ') + : '-' + : '-', qty: formatNumber(item.qty || 0), weight: formatNumber(item.weight || 0), avgWeight: formatNumber(item.average_weight || 0), @@ -66,11 +96,13 @@ export const generateCustomerPaymentExcel = async ( totalPrice: formatCurrency(item.total_price || 0), paymentAmount: formatCurrency(item.payment_amount || 0), accountsReceivable: formatCurrency(item.accounts_receivable || 0), - status: item.status || '', + status: item.status || '-', pickupInfo: Array.isArray(item.pickup_info) - ? item.pickup_info.join(', ') - : '', - salesPerson: item.sales_person || '', + ? item.pickup_info.length > 0 + ? item.pickup_info.join(', ') + : '-' + : '-', + salesPerson: item.sales_person || '-', }); const accountsReceivableCell = row.getCell('accountsReceivable'); diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index c6bad2a4..7b94de7d 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -364,7 +364,11 @@ const CustomerPaymentTab = () => { enableSorting: false, cell: (props) => { const value = props.row.original.vehicle_numbers; - return Array.isArray(value) ? value.join(', ') : value || '-'; + return Array.isArray(value) + ? value.length > 0 + ? value.join(', ') + : '-' + : '-'; }, }, { @@ -528,7 +532,11 @@ const CustomerPaymentTab = () => { enableSorting: false, cell: (props) => { const value = props.row.original.pickup_info; - return Array.isArray(value) ? value.join(', ') : value || '-'; + return Array.isArray(value) + ? value.length > 0 + ? value.join(', ') + : '-' + : '-'; }, }, { From 8c976b6d0bda23f30510a252feb3f6a6953db887 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 10:54:15 +0700 Subject: [PATCH 02/23] refactor(FE): Center PDF status and refine payment badge styling --- .../pages/report/finance/export/CustomerPaymentExportPDF.tsx | 2 +- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 77e33866..d4f6587a 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -479,7 +479,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatCurrency(item.accounts_receivable)} - + {item.status ? ( { return ( - {getPaymentStatusText(value)} + {getPaymentStatusText(value)} ); }, From 6a9f672d27a29efa7242285e04dceee4bccfe167 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 11:05:42 +0700 Subject: [PATCH 03/23] refactor(FE): Reduce font weight and size for payment badge --- src/components/pages/report/finance/tab/CustomerPaymentTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 3a4ae25f..e255174d 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -517,7 +517,7 @@ const CustomerPaymentTab = () => { size='sm' variant='soft' className={{ - badge: `py-2.5 px-2 font-medium text-base-content border border-gray-200 rounded-xl justify-start ${getPaymentStatusColor(value)}`, + badge: `py-2.5 px-2 font-thin text-xs border border-gray-200 rounded-xl justify-start ${getPaymentStatusColor(value)}`, status: getPaymentStatusIndicatorColor(value), }} > From 359f0f7b01ec9dd3d6d4ee09ba8f980855a3e022 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 11:10:08 +0700 Subject: [PATCH 04/23] refactor(FE): Move EKSPEDISI flag to supplier select --- .../pages/inventory/movement/form/MovementForm.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index d36fb067..d4c0c838 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -95,9 +95,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { isLoadingOptions: isLoadingWarehouses, loadMore: loadMoreWarehouses, rawData: warehouses, - } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search', { - flag: 'EKSPEDISI', - }); + } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search'); // ===== SELECT INPUT DATA ===== const { @@ -106,6 +104,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { isLoadingOptions: isLoadingSuppliers, } = useSelect(SupplierApi.basePath, 'id', 'name', 'search', { category: 'BOP', + flag: 'EKSPEDISI', }); // ===== DATA PROCESSING ===== From bcc2f71623f4b24bc8e635acf440441ab76c2684 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 12:52:54 +0700 Subject: [PATCH 05/23] refactor(FE): Manage product and delivery quantity error toasts --- .../inventory/movement/form/MovementForm.tsx | 149 +++++++++++++++--- 1 file changed, 125 insertions(+), 24 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index d4c0c838..e4dd9c51 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -54,6 +54,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { const [selectedProducts, setSelectedProducts] = useState([]); const [selectedDeliveries, setSelectedDeliveries] = useState([]); const [formErrorList, setFormErrorList] = useState([]); + const [productQtyErrorShown, setProductQtyErrorShown] = useState(false); + const [deliveryQtyErrorShown, setDeliveryQtyErrorShown] = useState(false); // ===== FORM HANDLERS ===== const createMovementHandler = useCallback( @@ -465,17 +467,25 @@ 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.setFieldValue('products', updatedProducts); + + if (productQtyErrorShown) { + toast.dismiss(); + setProductQtyErrorShown(false); + } + }, + [productQtyErrorShown] + ); const bulkRemoveProduct = useCallback(() => { const updatedProducts = @@ -484,7 +494,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) ?? []; formik.setFieldValue('products', updatedProducts); setSelectedProducts([]); - }, [formik, selectedProducts, setSelectedProducts]); + + if (productQtyErrorShown) { + toast.dismiss(); + setProductQtyErrorShown(false); + } + }, [formik, selectedProducts, setSelectedProducts, productQtyErrorShown]); const handleProductChange = useCallback( (idx: number, val: OptionType | OptionType[] | null) => { @@ -544,17 +559,28 @@ 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.setFieldValue('deliveries', updatedDeliveries); + + if (deliveryQtyErrorShown) { + toast.dismiss(); + setDeliveryQtyErrorShown(false); + } + }, + [deliveryQtyErrorShown] + ); const bulkRemoveDelivery = useCallback(() => { const updatedDeliveries = @@ -563,7 +589,17 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) ?? []; formik.setFieldValue('deliveries', updatedDeliveries); setSelectedDeliveries([]); - }, [formik, selectedDeliveries, setSelectedDeliveries]); + + if (deliveryQtyErrorShown) { + toast.dismiss(); + setDeliveryQtyErrorShown(false); + } + }, [ + formik, + selectedDeliveries, + setSelectedDeliveries, + deliveryQtyErrorShown, + ]); const handleDeliverySelectAllChange = useCallback( (e: React.ChangeEvent) => { @@ -1038,6 +1074,73 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.errors.destination_warehouse_id, ]); + useEffect(() => { + if (productQtyErrorShown) { + toast.dismiss(); + setProductQtyErrorShown(false); + } + }, [formik.values.products?.map((p) => p.product_qty).join(',')]); + + useEffect(() => { + if (deliveryQtyErrorShown) { + toast.dismiss(); + setDeliveryQtyErrorShown(false); + } + }, [ + formik.values.deliveries + ?.map((d) => d.products.map((p) => p.product_qty).join(',')) + .join('|'), + ]); + + useEffect(() => { + if (hasExceededStock && !productQtyErrorShown && type !== 'detail') { + const firstErrorIndex = formik.values.products?.findIndex( + (product, idx) => getProductQtyError(idx) !== null + ); + if (firstErrorIndex !== undefined && firstErrorIndex >= 0) { + const errorMsg = getProductQtyError(firstErrorIndex); + if (errorMsg) { + toast.error(errorMsg, { duration: Infinity }); + setProductQtyErrorShown(true); + } + } + } + }, [ + hasExceededStock, + productQtyErrorShown, + type, + formik.values.products, + getProductQtyError, + ]); + + useEffect(() => { + if (hasInvalidQty && !deliveryQtyErrorShown && type !== 'detail') { + const firstError = formik.values.deliveries?.find( + (delivery, deliveryIdx) => + delivery.products.some( + (product, productIdx) => + getDeliveryQtyError(deliveryIdx, productIdx) !== null + ) + ); + if (firstError) { + const deliveryIdx = formik.values.deliveries?.indexOf(firstError); + if (deliveryIdx !== undefined && deliveryIdx >= 0) { + const errorMsg = getDeliveryQtyError(deliveryIdx, 0); + if (errorMsg) { + toast.error(errorMsg, { duration: Infinity }); + setDeliveryQtyErrorShown(true); + } + } + } + } + }, [ + hasInvalidQty, + deliveryQtyErrorShown, + type, + formik.values.deliveries, + getDeliveryQtyError, + ]); + const handleValidateForm = async () => { const errors = await formik.validateForm(); @@ -1856,8 +1959,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { className='px-4' isLoading={formik.isSubmitting} disabled={ - hasInvalidQty || - hasExceededStock || formik.isSubmitting || (formik.values.source_warehouse_id === formik.values.destination_warehouse_id && From e35f8570576cc3cf573db6322ba8650e5b4eb0c1 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 12:58:33 +0700 Subject: [PATCH 06/23] fix(FE): Include deliveries in deps for cost handlers --- .../inventory/movement/form/MovementForm.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index e4dd9c51..eda03419 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -673,26 +673,29 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { [] ); - const handleDeliveryCostChange = useCallback((idx: number, value: number) => { - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value); + 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 + const delivery = formik.values.deliveries?.[idx]; + if (delivery) { + const productQty = delivery.products.reduce( + (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), + 0 ); - } else if (value === 0) { - formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 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); + } } - } - }, []); + }, + [formik.values.deliveries] + ); const handleDeliveryCostPerItemChange = useCallback( (idx: number, value: number) => { @@ -712,7 +715,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { } } }, - [] + [formik.values.deliveries] ); const handleDeliveryCostChangeWrapper = useCallback( From 1d29f62bf2129cad4e23ccdb76fc4ae8063ec366 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 13:10:54 +0700 Subject: [PATCH 07/23] refactor(FE): Fix delivery dependency tracking and init --- .../inventory/movement/form/MovementForm.tsx | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index eda03419..4d42efec 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -56,6 +56,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { const [formErrorList, setFormErrorList] = useState([]); const [productQtyErrorShown, setProductQtyErrorShown] = useState(false); const [deliveryQtyErrorShown, setDeliveryQtyErrorShown] = useState(false); + const [isInitialized, setIsInitialized] = useState(false); // ===== FORM HANDLERS ===== const createMovementHandler = useCallback( @@ -557,7 +558,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ], }, ]); - }, []); + }, [formik.values.deliveries]); const removeDelivery = useCallback( (i: number) => { @@ -1004,20 +1005,29 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }); }, [ formik.values.deliveries - ?.map((d) => - d.products.reduce( + ?.map((d, idx) => ({ + idx, + productQty: d.products.reduce( (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), 0 - ) + ), + deliveryCost: parseInt((d.delivery_cost || '').toString()) || 0, + deliveryCostPerItem: + parseInt((d.delivery_cost_per_item || '').toString()) || 0, + })) + .map( + (item) => + `${item.idx}:${item.productQty}:${item.deliveryCost}:${item.deliveryCostPerItem}` ) - .join(','), + .join('|'), ]); useEffect(() => { if ( formik.values.source_warehouse_id && type !== 'edit' && - type !== 'detail' + type !== 'detail' && + !isInitialized ) { if (formik.values.products.length === 0) { formik.setFieldValue('products', [ @@ -1049,8 +1059,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, ]); } + setIsInitialized(true); } - }, [formik.values.source_warehouse_id]); + }, [formik.values.source_warehouse_id, isInitialized, type]); useEffect(() => { if ( From 1b3dd34add0f3880b73a51487b870e9a03229819 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 13:21:12 +0700 Subject: [PATCH 08/23] refactor(FE): Filter products by available stock and format qty --- .../inventory/movement/form/MovementForm.tsx | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 4d42efec..d944b19b 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -18,6 +18,7 @@ import { Movement, } from '@/types/api/inventory/movement'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { formatNumber } from '@/lib/helper'; import { useRouter } from 'next/navigation'; import { MovementFormSchema, @@ -740,12 +741,32 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { return ( formik.values.products ?.filter((p) => p.product) - .map((p) => ({ - value: p.product_id, - label: (p.product as OptionType)?.label, - })) ?? [] + .map((p) => { + const totalQtyUsed = + formik.values.deliveries?.reduce((total, d) => { + const productQty = d.products.reduce((sum, deliveryProduct) => { + if (deliveryProduct.product_id === p.product_id) { + return sum + (Number(deliveryProduct.product_qty) || 0); + } + return sum; + }, 0); + return total + productQty; + }, 0) || 0; + + const availableQty = Number(p.product_qty) - totalQtyUsed; + + if (availableQty > 0) { + return { + value: p.product_id, + label: (p.product as OptionType)?.label, + }; + } + + return null; + }) + .filter((option) => option !== null) ?? [] ); - }, [formik.values.products]); + }, [formik.values.products, formik.values.deliveries]); const getAvailableStock = useCallback( (productId: number) => { @@ -769,10 +790,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { const remainingStock = availableStock - requestedQty; if (requestedQty > 0) { - return `Sisa: ${remainingStock.toLocaleString('en-US')}`; + return `Sisa: ${formatNumber(remainingStock)}`; } - return `Tersedia: ${availableStock.toLocaleString('en-US')}`; + return `Tersedia: ${formatNumber(availableStock)}`; }, [formik.values.products, getAvailableStock, type] ); @@ -792,12 +813,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { if (!relatedProduct) return undefined; const totalQtyUsed = - formik.values.deliveries?.reduce((total, d, dIdx) => { - const productQty = d.products.reduce((sum, p, pIdx) => { - if ( - p.product_id === deliveryProduct.product_id && - !(dIdx === deliveryIdx && pIdx === productIdx) - ) { + formik.values.deliveries?.reduce((total, d) => { + const productQty = d.products.reduce((sum, p) => { + if (p.product_id === deliveryProduct.product_id) { return sum + (Number(p.product_qty) || 0); } return sum; @@ -806,7 +824,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, 0) || 0; const availableQty = Number(relatedProduct.product_qty) - totalQtyUsed; - return `Tersedia: ${availableQty.toLocaleString('en-US')}`; + + const displayQty = availableQty > 0 ? availableQty : 0; + + return `Tersedia: ${formatNumber(displayQty)}`; }, [formik.values.deliveries, formik.values.products, type] ); @@ -856,7 +877,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { const requestedQty = Number(product.product_qty) || 0; if (requestedQty > availableStock) { - return `Qty melebihi stok tersedia! Maksimal: ${availableStock.toLocaleString('en-US')}`; + return `Qty melebihi stok tersedia! Maksimal: ${formatNumber(availableStock)}`; } return null; From 1d0d42dc16758f1867f27f51b8191d02f3db3ae6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 13:45:55 +0700 Subject: [PATCH 09/23] refactor(FE): Include products in MovementForm effect deps --- src/components/pages/inventory/movement/form/MovementForm.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index d944b19b..6cc3762b 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -1125,6 +1125,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.values.deliveries ?.map((d) => d.products.map((p) => p.product_qty).join(',')) .join('|'), + formik.values.products?.map((p) => p.product_qty).join(','), ]); useEffect(() => { @@ -1173,6 +1174,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { deliveryQtyErrorShown, type, formik.values.deliveries, + formik.values.products, getDeliveryQtyError, ]); From 27c696c7977bd151fe793f321a860c27e83601b2 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 15:37:06 +0700 Subject: [PATCH 10/23] refactor(FE): Use filter to remove products and deliveries --- .../inventory/movement/form/MovementForm.tsx | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 6cc3762b..817facf1 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -471,14 +471,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { const removeProduct = useCallback( (i: number) => { - const updatedProducts = - formik.values.products?.reduce((acc: ProductSchema[], item, index) => { - if (index !== i) { - acc.push(item); - } - return acc; - }, []) ?? []; - + const updatedProducts = formik.values.products?.filter( + (_, idx) => idx !== i + ); formik.setFieldValue('products', updatedProducts); if (productQtyErrorShown) { @@ -486,7 +481,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { setProductQtyErrorShown(false); } }, - [productQtyErrorShown] + [formik.values.products, productQtyErrorShown] ); const bulkRemoveProduct = useCallback(() => { @@ -563,25 +558,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 updatedDeliveries = formik.values.deliveries?.filter( + (_, idx) => idx !== i + ); formik.setFieldValue('deliveries', updatedDeliveries); + setSelectedDeliveries([]); + if (deliveryQtyErrorShown) { toast.dismiss(); setDeliveryQtyErrorShown(false); } }, - [deliveryQtyErrorShown] + [formik.values.deliveries, deliveryQtyErrorShown, setSelectedDeliveries] ); const bulkRemoveDelivery = useCallback(() => { @@ -1682,7 +1671,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { {formik.values.deliveries?.map((delivery, idx) => ( - + {type !== 'detail' && ( Date: Fri, 23 Jan 2026 16:05:59 +0700 Subject: [PATCH 11/23] refactor(FE): Exclude selected products from product options --- .../inventory/movement/form/MovementForm.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 817facf1..e07dcd78 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -467,7 +467,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, ]; formik.setFieldValue('products', newProducts); - }, []); + }, [formik.values.products]); const removeProduct = useCallback( (i: number) => { @@ -476,12 +476,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ); formik.setFieldValue('products', updatedProducts); + setSelectedProducts([]); + if (productQtyErrorShown) { toast.dismiss(); setProductQtyErrorShown(false); } }, - [formik.values.products, productQtyErrorShown] + [formik.values.products, productQtyErrorShown, setSelectedProducts] ); const bulkRemoveProduct = useCallback(() => { @@ -725,7 +727,20 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { [handleDeliveryCostPerItemChange] ); - // UTILITY FUNCTIONS + const getAvailableProductOptions = useCallback( + (currentIdx: number) => { + const selectedProductIds = + formik.values.products + ?.filter((p, idx) => idx !== currentIdx && p.product_id !== 0) + .map((p) => p.product_id) || []; + + return productWarehouseOptions.filter( + (pw) => !selectedProductIds.includes(pw.product_id) + ); + }, + [formik.values.products, productWarehouseOptions] + ); + const getFilteredProductWarehouseOptions = useCallback(() => { return ( formik.values.products @@ -1468,7 +1483,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { required value={product.product ?? undefined} onChange={(val) => handleProductChange(idx, val)} - options={productWarehouseOptions} + options={getAvailableProductOptions(idx)} onInputChange={setProductWarehouseSelectInputValue} onMenuScrollToBottom={loadMoreProductWarehouses} isLoading={isLoadingProductWarehouses} From 5c286128e4727eebce3e73a9da591f840255f3a6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 16:13:36 +0700 Subject: [PATCH 12/23] refactor(FE): Exclude falsy product_id in product filter --- src/components/pages/inventory/movement/form/MovementForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index e07dcd78..a73bd634 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -731,7 +731,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { (currentIdx: number) => { const selectedProductIds = formik.values.products - ?.filter((p, idx) => idx !== currentIdx && p.product_id !== 0) + ?.filter((p, idx) => { + return idx !== currentIdx && p.product_id && p.product_id !== 0; + }) .map((p) => p.product_id) || []; return productWarehouseOptions.filter( From 6f5540eb91f2adc025f3b04b730bc4ffd77eb9b7 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 16:26:25 +0700 Subject: [PATCH 13/23] refactor(FE): Reset deliveries when products change --- .../inventory/movement/form/MovementForm.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index a73bd634..e035c53a 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -1115,6 +1115,44 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.errors.destination_warehouse_id, ]); + useEffect(() => { + if (formik.values.products && formik.values.deliveries) { + const productIds = formik.values.products.map((p) => p.product_id); + + const updatedDeliveries = formik.values.deliveries.map((delivery) => { + const deliveryProduct = delivery.products[0]; + if (deliveryProduct && deliveryProduct.product_id !== 0) { + if (!productIds.includes(deliveryProduct.product_id)) { + return { + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + delivery_cost: '', + delivery_cost_per_item: '', + }; + } + } + return delivery; + }); + + const hasChanges = formik.values.deliveries.some( + (delivery, idx) => + delivery.products[0]?.product_id !== + updatedDeliveries[idx]?.products[0]?.product_id || + delivery.delivery_cost !== updatedDeliveries[idx]?.delivery_cost + ); + + if (hasChanges) { + formik.setFieldValue('deliveries', updatedDeliveries); + } + } + }, [formik.values.products]); + useEffect(() => { if (productQtyErrorShown) { toast.dismiss(); From 6af2609f440edf53dd3cc4ec1dc598626bc7ac67 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 16:38:34 +0700 Subject: [PATCH 14/23] refactor(FE): Use useSelect to fetch product warehouses --- .../inventory/movement/form/MovementForm.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index e035c53a..b6a40cc3 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -86,13 +86,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { quantity: number; } - // ===== API DATA FETCHING ===== - const allProductWarehousesUrl = `${ProductWarehouseApi.basePath}`; - const { data: allProductWarehouses } = useSWR( - allProductWarehousesUrl, - ProductWarehouseApi.getAllFetcher - ); - // ===== USE SELECT HOOKS ===== const { setInputValue: setWarehouseSelectInputValue, @@ -101,6 +94,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { rawData: warehouses, } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search'); + const { rawData: allProductWarehouses } = useSelect( + ProductWarehouseApi.basePath, + 'id', + 'product.name', + 'search', + { limit: '100' } + ); + // ===== SELECT INPUT DATA ===== const { setInputValue: setSupplierSelectInputValue, From e386d2a3892db60d17d6b917febcd79a0077b562 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 18:12:44 +0700 Subject: [PATCH 15/23] chore(FE-Cleanup): Clean up unused code and add memoization --- .../HppExpeditionReportTable.tsx | 1 - .../pages/closing/sale/SalesReportTable.tsx | 6 +---- .../pages/expense/ExpenseRequestContent.tsx | 7 +---- .../pages/expense/ExpensesTable.tsx | 27 ------------------- ...ExpenseRealizationKandangDetailExpense.tsx | 2 +- .../inventory/movement/form/MovementForm.tsx | 23 ++++++++-------- .../master-data/product/form/ProductForm.tsx | 4 +-- .../recording/form/RecordingForm.tsx | 8 ------ .../production/uniformity/UniformityTable.tsx | 2 -- .../uniformity/chart/UniformityGaugeChart.tsx | 2 +- .../uniformity/form/UniformityForm.tsx | 6 +---- .../skeleton/UniformityGaugeChartSkeleton.tsx | 2 +- 12 files changed, 19 insertions(+), 71 deletions(-) diff --git a/src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx index f683ec58..da89d963 100644 --- a/src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx +++ b/src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx @@ -13,7 +13,6 @@ interface HppExpeditionReportTableProps { } const HppExpeditionReportTable = ({ - type = 'detail', initialValues, }: HppExpeditionReportTableProps) => { const costOfRevenueExpeditionData: BaseExpeditionCost[] = useMemo(() => { diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index 99868984..0632676b 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -4,7 +4,6 @@ import React, { useMemo } from 'react'; import { ColumnDef } from '@tanstack/react-table'; import Table from '@/components/Table'; import Card from '@/components/Card'; -import Badge from '@/components/Badge'; import { formatCurrency, formatNumber, formatDate } from '@/lib/helper'; import { BaseClosingSales, @@ -20,10 +19,7 @@ interface SalesReportTableProps { initialValues?: BaseClosingSales; } -const SalesReportTable = ({ - type = 'detail', - initialValues, -}: SalesReportTableProps) => { +const SalesReportTable = ({ initialValues }: SalesReportTableProps) => { const salesData: BaseSales[] = useMemo(() => { return initialValues?.sales || []; }, [initialValues]); diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index ac814bcf..6513956e 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -1,9 +1,8 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { useFormik } from 'formik'; -import useSWR from 'swr'; import toast from 'react-hot-toast'; import { Icon } from '@iconify/react'; @@ -57,10 +56,6 @@ const ExpenseRequestContent = ({ const isLatestApprovalRejected = initialValues?.latest_approval.action === 'REJECTED'; - const isLatestApprovalRejectedOrDone = - isLatestApprovalRejected || - initialValues?.latest_approval.step_number === 6; - const isCurrentApprovalOnHeadArea = !isLatestApprovalRejected && initialValues?.latest_approval.step_number === 1; diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index 895c0997..69376992 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -35,7 +35,6 @@ import { ExpenseApi } from '@/services/api/expense'; import { cn, formatCurrency, formatDate } from '@/lib/helper'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; -import { ROWS_OPTIONS } from '@/config/constant'; import { LocationApi, SupplierApi } from '@/services/api/master-data'; import { Location } from '@/types/api/master-data/location'; import { Supplier } from '@/types/api/master-data/supplier'; @@ -44,8 +43,6 @@ import { BaseApiResponse } from '@/types/api/api-general'; const RowOptionsMenu = ({ type = 'dropdown', props, - approveClickHandler, - rejectClickHandler, deleteClickHandler, }: { type: 'dropdown' | 'collapse'; @@ -186,7 +183,6 @@ const ExpensesTable = () => { undefined ); const [isDeleteLoading, setIsDeleteLoading] = useState(false); - const [isCompleteLoading, setIsCompleteLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); @@ -247,23 +243,6 @@ const ExpensesTable = () => { }); }, [expenses, selectedRowIds]); - const isAllSelectedRowLatestApprovalOnRealization = useMemo(() => { - return selectedRowIds.every((rowId) => { - if (!isResponseSuccess(expenses)) return false; - - const expenseItem = expenses.data.find((item) => item.id === rowId); - - const isLatestApprovalRejected = - expenseItem?.latest_approval.action === 'REJECTED'; - - const isCurrentApprovalOnRealization = - !isLatestApprovalRejected && - expenseItem?.latest_approval.step_number === 5; - - return isCurrentApprovalOnRealization; - }); - }, [expenses, selectedRowIds]); - const expensesColumns: ColumnDef[] = [ { id: 'select', @@ -589,12 +568,6 @@ const ExpensesTable = () => { updateFilter('realizationDate', e.target.value); }; - const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - - setPageSize(newVal.value as number); - }; - // track sorting useEffect(() => { const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); diff --git a/src/components/pages/expense/form/ExpenseRealizationKandangDetailExpense.tsx b/src/components/pages/expense/form/ExpenseRealizationKandangDetailExpense.tsx index 3f6f2220..fcf367eb 100644 --- a/src/components/pages/expense/form/ExpenseRealizationKandangDetailExpense.tsx +++ b/src/components/pages/expense/form/ExpenseRealizationKandangDetailExpense.tsx @@ -30,7 +30,7 @@ interface ExpenseRealizationKandangDetailExpenseProps { const ExpenseRealizationKandangDetailExpense: React.FC< ExpenseRealizationKandangDetailExpenseProps -> = ({ type, formik, supplierId, location, className }) => { +> = ({ formik, supplierId, location, className }) => { const { setInputValue: setNonstockInputValue, options: nonstockOptions, diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index b6a40cc3..bdf37487 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useFormik } from 'formik'; -import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Button from '@/components/Button'; @@ -326,16 +325,18 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { } ); - const productWarehouseOptions = isResponseSuccess(productWarehouses) - ? productWarehouses?.data.map((pw) => ({ - value: pw.product.id, - label: pw.product.name, - product_id: pw.product.id, - warehouse_id: pw.warehouse.id, - warehouse_name: pw.warehouse.name, - quantity: pw.quantity, - })) - : []; + const productWarehouseOptions = useMemo(() => { + return isResponseSuccess(productWarehouses) + ? productWarehouses?.data.map((pw) => ({ + value: pw.product.id, + label: pw.product.name, + product_id: pw.product.id, + warehouse_id: pw.warehouse.id, + warehouse_name: pw.warehouse.name, + quantity: pw.quantity, + })) + : []; + }, [productWarehouses]); // ===== HELPER FUNCTIONS ===== const isRepeaterInputError = ( diff --git a/src/components/pages/master-data/product/form/ProductForm.tsx b/src/components/pages/master-data/product/form/ProductForm.tsx index 8c04d594..45ba192d 100644 --- a/src/components/pages/master-data/product/form/ProductForm.tsx +++ b/src/components/pages/master-data/product/form/ProductForm.tsx @@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useFormik } from 'formik'; import { toast } from 'react-hot-toast'; -import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Button from '@/components/Button'; @@ -17,7 +16,6 @@ import SelectInput, { import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import RequirePermission from '@/components/helper/RequirePermission'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; import { @@ -25,7 +23,7 @@ import { ProductFormValues, UpdateProductFormSchema, } from '@/components/pages/master-data/product/form/ProductForm.schema'; -import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError } from '@/lib/api-helper'; import { Product, CreateProductPayload, diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index a000f4a3..63d78397 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -71,7 +71,6 @@ import { import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { formatDate, formatNumber } from '@/lib/helper'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; import toast from 'react-hot-toast'; import ApprovalSteps, { useApprovalSteps, @@ -423,7 +422,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options: locationOptions, isLoadingOptions: isLoadingLocations, loadMore: loadMoreLocations, - hasMore: hasMoreLocations, } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); const { @@ -432,7 +430,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { rawData: projectFlocksRawData, isLoadingOptions: isLoadingProjectFlocks, loadMore: loadMoreProjectFlocks, - hasMore: hasMoreProjectFlocks, } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { location_id: selectedProjectFlockLocationId, }); @@ -531,7 +528,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { rawData: stockProducts, isLoadingOptions: isLoadingStockProducts, loadMore: loadMoreStockProducts, - hasMore: hasMoreStockProducts, } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { flags: 'PAKAN,OVK', location_id: stockProductsLocationId, @@ -539,11 +535,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }); const { - options: depletionProductOptions, rawData: depletionProductsData, isLoadingOptions: isLoadingDepletionProducts, loadMore: loadMoreDepletionProducts, - hasMore: hasMoreDepletionProducts, } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { location_id: depletionProductsLocationId, kandang_id: depletionProductsKandangId, @@ -584,11 +578,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [nextDayRecordingData]); const { - options: eggProductOptions, rawData: eggProductsData, isLoadingOptions: isLoadingEggProducts, loadMore: loadMoreEggProducts, - hasMore: hasMoreEggProducts, } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { search: 'telur', location_id: eggProductsLocationId, diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index c2049ab1..fafa3d77 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -241,7 +241,6 @@ const UniformityTable = () => { options: filterLocationOptions, isLoadingOptions: isLoadingFilterLocations, loadMore: loadMoreFilterLocations, - hasMore: hasMoreFilterLocations, } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); // ===== FETCH PROJECT FLOCKS DATA FOR FILTER ===== @@ -251,7 +250,6 @@ const UniformityTable = () => { rawData: filterProjectFlocksRawData, isLoadingOptions: isLoadingFilterProjectFlocks, loadMore: loadMoreFilterProjectFlocks, - hasMore: hasMoreFilterProjectFlocks, } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { location_id: filterProjectFlockLocationId, }); diff --git a/src/components/pages/production/uniformity/chart/UniformityGaugeChart.tsx b/src/components/pages/production/uniformity/chart/UniformityGaugeChart.tsx index 04fbef9c..54f8e4ec 100644 --- a/src/components/pages/production/uniformity/chart/UniformityGaugeChart.tsx +++ b/src/components/pages/production/uniformity/chart/UniformityGaugeChart.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts'; import Card from '@/components/Card'; import { formatNumber } from '@/lib/helper'; diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index f46a15b3..ae687213 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -87,9 +87,7 @@ const UniformityForm = ({ const fileInputRef = useRef(null); // ===== SELECT INPUT DATA ===== - const [selectedLocation, setSelectedLocation] = useState( - null - ); + const [, setSelectedLocation] = useState(null); const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] = useState(''); @@ -106,7 +104,6 @@ const UniformityForm = ({ options: locationOptions, isLoadingOptions: isLoadingLocations, loadMore: loadMoreLocations, - hasMore: hasMoreLocations, } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); const { @@ -115,7 +112,6 @@ const UniformityForm = ({ rawData: projectFlocksRawData, isLoadingOptions: isLoadingProjectFlocks, loadMore: loadMoreProjectFlocks, - hasMore: hasMoreProjectFlocks, } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { location_id: selectedProjectFlockLocationId, }); diff --git a/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx b/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx index 436fab9a..d9031163 100644 --- a/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx +++ b/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx @@ -29,7 +29,7 @@ const UniformityGaugeChartSkeleton: React.FC< return (
-
+
From 9f6fec5a3c5e34d493f44aec9f84e0283b6ff069 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 20:55:48 +0700 Subject: [PATCH 16/23] refactor(FE): Set uniformity week from recording data --- .../uniformity/form/UniformityForm.tsx | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index ae687213..3ae722b2 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -5,7 +5,6 @@ import { useFormik } from 'formik'; import { useRouter } from 'next/navigation'; import { Icon } from '@iconify/react'; import { toast } from 'react-hot-toast'; -import moment from 'moment'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import { useUiStore } from '@/stores/ui/ui.store'; import { useUniformityStore } from '@/stores/uniformity/uniformity.store'; @@ -28,6 +27,7 @@ import { LocationApi } from '@/services/api/master-data'; import { ProjectFlockApi, ProjectFlockKandangApi, + RecordingApi, } from '@/services/api/production'; import { UniformityApi } from '@/services/api/uniformity'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; @@ -40,6 +40,7 @@ import { ProjectFlockKandangLookup, ProjectFlock, } from '@/types/api/production/project-flock'; +import { Recording } from '@/types/api/production/recording'; import { Kandang } from '@/types/api/master-data/kandang'; import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm'; import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm'; @@ -200,6 +201,20 @@ const UniformityForm = ({ ? projectFlockKandangLookupData.data : undefined; + // ===== RECORDINGS DATA (FOR WEEK CALCULATION) ===== + const recordingsUrl = useMemo(() => { + const params = new URLSearchParams({ + page: '1', + limit: '100', + }); + return `${RecordingApi.basePath}?${params.toString()}`; + }, []); + + const { data: recordingsData } = useSWR( + recordingsUrl, + RecordingApi.getAllFetcher + ); + // ===== FORM CONFIGURATION ===== const formikInitialValues = useMemo( () => getUniformityFormInitialValues(initialValues), @@ -383,14 +398,24 @@ const UniformityForm = ({ // ===== SIDE EFFECTS ===== useEffect(() => { - if (formik.values.date) { - const date = moment(formik.values.date); - const weekNumber = date.week() - moment(date).startOf('month').week() + 1; - const adjustedWeekNumber = weekNumber <= 0 ? weekNumber + 52 : weekNumber; + if ( + projectFlockKandangLookup?.project_flock_kandang_id && + isResponseSuccess(recordingsData) && + recordingsData.data + ) { + const matchingRecording = recordingsData.data.find( + (recording: Recording) => + recording.project_flock?.project_flock_kandang_id === + projectFlockKandangLookup.project_flock_kandang_id + ); - formik.setFieldValue('week', adjustedWeekNumber); + if (matchingRecording?.project_flock?.production_standart?.week) { + const weekValue = + matchingRecording.project_flock.production_standart.week; + formik.setFieldValue('week', weekValue); + } } - }, [formik.values.date]); + }, [projectFlockKandangLookup?.project_flock_kandang_id, recordingsData]); useEffect(() => { const unsub = subscribeValidate(() => { From b046b64ed2cfd346cc7e1ff47be58e3fe989c642 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 21:32:10 +0700 Subject: [PATCH 17/23] refactor(FE): Add Formik-based filter with validation --- .../production/uniformity/UniformityTable.tsx | 222 +++++++++++------- .../UniformityTableFilter.schema.ts | 66 ++++++ 2 files changed, 207 insertions(+), 81 deletions(-) create mode 100644 src/components/pages/production/uniformity/UniformityTableFilter.schema.ts diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index fafa3d77..5b6bf892 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -51,6 +51,13 @@ import { generateUniformityExcel } from '@/components/pages/production/uniformit import Dropdown from '@/components/Dropdown'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; +import { useFormik } from 'formik'; +import { + UniformityTableFilterSchema, + type UniformityTableFilterValues, +} from '@/components/pages/production/uniformity/UniformityTableFilter.schema'; +import AlertErrorList from '@/components/helper/form/FormErrors'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; const UniformityConfirmationPreview = ({ uniformity, @@ -314,6 +321,37 @@ const UniformityTable = () => { } }, [projectFlockKandangLookup]); + // ===== FORMIK FILTER ===== + const filterFormik = useFormik({ + initialValues: { + start_date: filterStartDate, + end_date: filterEndDate, + location: filterLocation, + location_id: Number(filterLocation?.value) || 0, + project_flock: filterProjectFlock, + project_flock_id: Number(filterProjectFlock?.value) || 0, + project_flock_kandang_id: filterProjectFlockKandangId, + kandang: filterKandang, + kandang_id: Number(filterKandang?.value) || 0, + }, + validationSchema: UniformityTableFilterSchema, + enableReinitialize: true, + onSubmit: async (values) => { + setFilterStartDate(values.start_date); + setFilterEndDate(values.end_date); + setFilterLocation(values.location ?? null); + setFilterProjectFlock(values.project_flock ?? null); + setFilterKandang(values.kandang ?? null); + + setIsSubmitted(true); + filterModal.closeModal(); + }, + }); + + // ===== FORMIK ERROR LIST ===== + const { formErrorList, close, handleFormSubmit } = + useFormikErrorList(filterFormik); + // ===== BUILD SWR KEY WITH FILTERS ===== const uniformitySwrKey = useMemo(() => { const basePath = UniformityApi.basePath; @@ -370,29 +408,54 @@ const UniformityTable = () => { const handleFilterLocationChange = useCallback( (val: OptionType | OptionType[] | null) => { const location = val as OptionType | null; + const locationId = Number(location?.value) || 0; + + filterFormik.setFieldValue('location', location); + filterFormik.setFieldValue('location_id', locationId); + setFilterLocation(location); setFilterProjectFlock(null); setFilterKandang(null); setFilterProjectFlockLocationId( location ? location.value.toString() : '' ); + + filterFormik.setFieldValue('project_flock', null); + filterFormik.setFieldValue('project_flock_id', 0); + filterFormik.setFieldValue('kandang', null); + filterFormik.setFieldValue('kandang_id', 0); }, - [] + [filterFormik] ); const handleFilterProjectFlockChange = useCallback( (val: OptionType | OptionType[] | null) => { - setFilterProjectFlock(val as OptionType | null); + const projectFlock = val as OptionType | null; + const projectFlockId = Number(projectFlock?.value) || 0; + + filterFormik.setFieldValue('project_flock', projectFlock); + filterFormik.setFieldValue('project_flock_id', projectFlockId); + + setFilterProjectFlock(projectFlock); setFilterKandang(null); + + filterFormik.setFieldValue('kandang', null); + filterFormik.setFieldValue('kandang_id', 0); }, - [] + [filterFormik] ); const handleFilterKandangChange = useCallback( (val: OptionType | OptionType[] | null) => { - setFilterKandang(val as OptionType | null); + const kandang = val as OptionType | null; + const kandangId = Number(kandang?.value) || 0; + + filterFormik.setFieldValue('kandang', kandang); + filterFormik.setFieldValue('kandang_id', kandangId); + + setFilterKandang(kandang); }, - [] + [filterFormik] ); const handleResetFilters = useCallback(() => { @@ -403,41 +466,34 @@ const UniformityTable = () => { setFilterProjectFlockKandangId(undefined); setFilterStartDate(''); setFilterEndDate(''); - }, []); + setFilterErrors({}); + + filterFormik.resetForm(); + }, [filterFormik]); + + const handleFilterStartDateChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setFilterStartDate(value); + filterFormik.setFieldValue('start_date', value); + }, + [filterFormik] + ); + + const handleFilterEndDateChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setFilterEndDate(value); + filterFormik.setFieldValue('end_date', value); + }, + [filterFormik] + ); const handleApplyFilters = useCallback(() => { - const errors: Record = {}; - - if (!filterStartDate) { - errors.start_date = 'Tanggal mulai wajib diisi'; - } - if (!filterEndDate) { - errors.end_date = 'Tanggal akhir wajib diisi'; - } - if (!filterLocation) { - errors.location = 'Lokasi wajib dipilih'; - } - if (!filterProjectFlock) { - errors.project_flock = 'Project Flock wajib dipilih'; - } - if (!filterKandang) { - errors.kandang = 'Kandang wajib dipilih'; - } - - setFilterErrors(errors); - - if (Object.keys(errors).length === 0) { - setIsSubmitted(true); - filterModal.closeModal(); - } - }, [ - filterModal, - filterStartDate, - filterEndDate, - filterLocation, - filterProjectFlock, - filterKandang, - ]); + handleFormSubmit( + new Event('submit') as unknown as React.FormEvent + ); + }, [handleFormSubmit]); const selectedRowIds = useMemo(() => { return Object.keys(rowSelection) @@ -1134,42 +1190,46 @@ const UniformityTable = () => {
+ + {/* Error List Alert */} + {formErrorList.length > 0 && ( +
+ +
+ )} +
{ - setFilterStartDate(e.target.value); - setFilterErrors((prev) => ({ ...prev, start_date: '' })); - }} + value={filterFormik.values.start_date} + onChange={handleFilterStartDateChange} + onBlur={filterFormik.handleBlur} + isError={ + filterFormik.touched.start_date && + Boolean(filterFormik.errors.start_date) + } + errorMessage={filterFormik.errors.start_date} className={{ wrapper: 'w-full' }} /> - {filterErrors.start_date && ( -

- {filterErrors.start_date} -

- )}
{ - setFilterEndDate(e.target.value); - setFilterErrors((prev) => ({ ...prev, end_date: '' })); - }} + value={filterFormik.values.end_date} + onChange={handleFilterEndDateChange} + onBlur={filterFormik.handleBlur} + isError={ + filterFormik.touched.end_date && + Boolean(filterFormik.errors.end_date) + } + errorMessage={filterFormik.errors.end_date} className={{ wrapper: 'w-full' }} /> - {filterErrors.end_date && ( -

- {filterErrors.end_date} -

- )}
@@ -1177,65 +1237,65 @@ const UniformityTable = () => { { handleFilterLocationChange(value); - setFilterErrors((prev) => ({ ...prev, location: '' })); }} options={filterLocationOptions} onInputChange={setFilterLocationInputValue} isLoading={isLoadingFilterLocations} onMenuScrollToBottom={loadMoreFilterLocations} + isError={ + filterFormik.touched.location && + Boolean(filterFormik.errors.location) + } + errorMessage={filterFormik.errors.location} + isClearable className={{ wrapper: 'w-full' }} /> - {filterErrors.location && ( -

- {filterErrors.location} -

- )}
{ handleFilterProjectFlockChange(value); - setFilterErrors((prev) => ({ ...prev, project_flock: '' })); }} options={filterProjectFlockOptions} onInputChange={setFilterProjectFlockSearchValue} isLoading={isLoadingFilterProjectFlocks} onMenuScrollToBottom={loadMoreFilterProjectFlocks} - isDisabled={!filterLocation} + isDisabled={!filterFormik.values.location} + isError={ + filterFormik.touched.project_flock && + Boolean(filterFormik.errors.project_flock) + } + errorMessage={filterFormik.errors.project_flock} + isClearable className={{ wrapper: 'w-full' }} /> - {filterErrors.project_flock && ( -

- {filterErrors.project_flock} -

- )}
{ handleFilterKandangChange(value); - setFilterErrors((prev) => ({ ...prev, kandang: '' })); }} options={filterKandangOptions} - isDisabled={!filterProjectFlock} + isDisabled={!filterFormik.values.project_flock} + isError={ + filterFormik.touched.kandang && + Boolean(filterFormik.errors.kandang) + } + errorMessage={filterFormik.errors.kandang} + isClearable className={{ wrapper: 'w-full' }} /> - {filterErrors.kandang && ( -

- {filterErrors.kandang} -

- )}
diff --git a/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts b/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts new file mode 100644 index 00000000..a95ea3e2 --- /dev/null +++ b/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts @@ -0,0 +1,66 @@ +import { OptionType } from '@/components/input/SelectInput'; +import * as yup from 'yup'; + +export type UniformityTableFilterType = { + start_date: string; + end_date: string; + location: OptionType | null; + location_id: number; + project_flock: OptionType | null; + project_flock_id: number; + project_flock_kandang_id: number | undefined; + kandang: OptionType | null; + kandang_id: number; +}; + +export const UniformityTableFilterSchema = yup.object({ + start_date: yup.string().required('Tanggal mulai wajib diisi'), + end_date: yup.string().required('Tanggal akhir wajib diisi'), + location: yup + .mixed() + .required('Lokasi wajib dipilih') + .test('is-not-empty', 'Lokasi wajib dipilih', (value) => { + if (Array.isArray(value)) { + return value.length > 0; + } + return !!value; + }), + location_id: yup + .number() + .min(1, 'Location wajib diisi!') + .required('Location wajib diisi!') + .typeError('Location wajib diisi!'), + project_flock: yup + .mixed() + .required('Project Flock wajib dipilih') + .test('is-not-empty', 'Project Flock wajib dipilih', (value) => { + if (Array.isArray(value)) { + return value.length > 0; + } + return !!value; + }), + project_flock_id: yup + .number() + .min(1, 'Project flock wajib diisi!') + .required('Project flock wajib diisi!') + .typeError('Project flock wajib diisi!'), + project_flock_kandang_id: yup.number().optional(), + kandang: yup + .mixed() + .required('Kandang wajib dipilih') + .test('is-not-empty', 'Kandang wajib dipilih', (value) => { + if (Array.isArray(value)) { + return value.length > 0; + } + return !!value; + }), + kandang_id: yup + .number() + .min(1, 'Kandang wajib diisi!') + .required('Kandang wajib diisi!') + .typeError('Kandang wajib diisi!'), +}) as yup.ObjectSchema; + +export type UniformityTableFilterValues = yup.InferType< + typeof UniformityTableFilterSchema +>; From 143674533a98154c25fe506e5bcb5b94c37dbb11 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 21:40:54 +0700 Subject: [PATCH 18/23] refactor(FE): Remove redundant ID fields and require filters and update date labels to 'Tanggal mulai' and 'Tanggal akhir --- .../production/uniformity/UniformityTable.tsx | 12 +++++++----- .../uniformity/UniformityTableFilter.schema.ts | 18 ------------------ 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index 5b6bf892..29d1155a 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -327,12 +327,9 @@ const UniformityTable = () => { start_date: filterStartDate, end_date: filterEndDate, location: filterLocation, - location_id: Number(filterLocation?.value) || 0, project_flock: filterProjectFlock, - project_flock_id: Number(filterProjectFlock?.value) || 0, project_flock_kandang_id: filterProjectFlockKandangId, kandang: filterKandang, - kandang_id: Number(filterKandang?.value) || 0, }, validationSchema: UniformityTableFilterSchema, enableReinitialize: true, @@ -1202,7 +1199,8 @@ const UniformityTable = () => {
{
{
{
{
() .required('Project Flock wajib dipilih') @@ -39,11 +31,6 @@ export const UniformityTableFilterSchema = yup.object({ } return !!value; }), - project_flock_id: yup - .number() - .min(1, 'Project flock wajib diisi!') - .required('Project flock wajib diisi!') - .typeError('Project flock wajib diisi!'), project_flock_kandang_id: yup.number().optional(), kandang: yup .mixed() @@ -54,11 +41,6 @@ export const UniformityTableFilterSchema = yup.object({ } return !!value; }), - kandang_id: yup - .number() - .min(1, 'Kandang wajib diisi!') - .required('Kandang wajib diisi!') - .typeError('Kandang wajib diisi!'), }) as yup.ObjectSchema; export type UniformityTableFilterValues = yup.InferType< From b7c0a80a04ba6e65745bbcb97707fadf08d17081 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 21:45:09 +0700 Subject: [PATCH 19/23] refactor(FE): Validate end_date is not before start_date --- .../uniformity/UniformityTableFilter.schema.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts b/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts index 6f7c1039..3c14553c 100644 --- a/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts +++ b/src/components/pages/production/uniformity/UniformityTableFilter.schema.ts @@ -12,7 +12,18 @@ export type UniformityTableFilterType = { export const UniformityTableFilterSchema = yup.object({ start_date: yup.string().required('Tanggal mulai wajib diisi'), - end_date: yup.string().required('Tanggal akhir wajib diisi'), + end_date: yup + .string() + .required('Tanggal akhir wajib diisi') + .test( + 'is-greater-than-start', + 'Tanggal akhir tidak boleh masa lampau', + function (value) { + const { start_date } = this.parent; + if (!start_date || !value) return true; + return new Date(value) >= new Date(start_date); + } + ), location: yup .mixed() .required('Lokasi wajib dipilih') From 762fb0856821f519eb63ef1c6ce93c28cd15e7e9 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 23 Jan 2026 21:48:57 +0700 Subject: [PATCH 20/23] refactor(FE): Replace hardcoded blue with bg-primary --- .../pages/production/uniformity/chart/UniformityBarChart.tsx | 4 ++-- .../pages/production/uniformity/form/UniformityForm.tsx | 4 ++-- .../uniformity/skeleton/UniformityBarChartSkeleton.tsx | 2 +- .../uniformity/skeleton/UniformityGaugeChartSkeleton.tsx | 2 +- .../uniformity/skeleton/UniformityTableSkeleton.tsx | 2 +- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx b/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx index 88d0dc59..82f0085c 100644 --- a/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx +++ b/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx @@ -49,7 +49,7 @@ function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
-
+
Ideal
@@ -84,7 +84,7 @@ function CustomTooltip({ payload, label, active }: CustomTooltipProps) {

Uniformity 2025

-
+
Ideal
{chartData.idealRange} diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index 3ae722b2..d7013520 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -619,7 +619,7 @@ const UniformityForm = ({
diff --git a/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx b/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx index d9031163..17ed7ee9 100644 --- a/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx +++ b/src/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton.tsx @@ -57,7 +57,7 @@ const UniformityGaugeChartSkeleton: React.FC<
-