From 8d586e7cb4e307060aeb2a555033740e7c24932e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 13:53:45 +0700 Subject: [PATCH 01/47] refactor(FE): Switch FinanceApi to production and remove import --- src/services/api/report/finance-report.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 9fa4f37c..3a564e32 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -1,7 +1,6 @@ import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; -import { DebtSupplier } from '@/types/api/report/debt-supplier'; export class FinanceApiService extends BaseApiService< CustomerPaymentReport, @@ -39,8 +38,8 @@ export class FinanceApiService extends BaseApiService< } } -// export const FinanceApi = new FinanceApiService('reports'); +export const FinanceApi = new FinanceApiService('reports'); -export const FinanceApi = new FinanceApiService( - 'http://localhost:4010/api/reports/finance' -); +// export const FinanceApi = new FinanceApiService( +// 'http://localhost:4010/api/reports/finance' +// ); From 01e94b57c15a29ebec567ac353baa3940f04cc23 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 14:14:48 +0700 Subject: [PATCH 02/47] refactor(FE): Disable row selection for approved recordings --- .../pages/production/recording/RecordingTable.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 6b10b26e..a99cfbbc 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -656,13 +656,23 @@ const RecordingTable = () => { ); }, cell: ({ row }) => { + const recording = row.original; + const isDisabled = isRecordingApproved(recording); + + const handleToggleSelection = (e: unknown) => { + if (!isDisabled) { + row.getToggleSelectedHandler()(e); + } + }; + return ( -
+
); From 66fa65e4bb24e4a3b250c85f8ecd6eeac1abf5ef Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 14:29:40 +0700 Subject: [PATCH 03/47] refactor(FE): Disable sales and filter_by until backend ready --- .../export/CustomerPaymentExportPDF.tsx | 18 +++-- .../report/finance/tab/CustomerPaymentTab.tsx | 71 ++++++++++--------- src/services/api/report/finance-report.ts | 10 ++- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 5adcb694..5a656e7a 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -177,10 +177,12 @@ interface CustomerPaymentExportPDFParams { data: CustomerPaymentReport[]; params?: { customer_name?: string; - sales?: string; + // TODO: Uncomment when BE is ready + // sales?: string; start_date?: string; end_date?: string; - filter_by?: string; + // TODO: Uncomment when BE is ready + // filter_by?: string; }; } @@ -195,9 +197,10 @@ const getParameterText = ( paramsText.push('Semua Customer'); } - if (params?.sales) { - paramsText.push(`Sales: ${params.sales}`); - } + // TODO: Uncomment when BE is ready + // if (params?.sales) { + // paramsText.push(`Sales: ${params.sales}`); + // } if (params?.start_date && params?.end_date) { const startDate = formatDate(params.start_date, 'DD MMM YYYY'); @@ -242,9 +245,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { : '-'} - + {/* TODO: Uncomment when BE is ready */} + {/* Filter Tanggal: Tanggal DO - + */} Customer: {params.params?.customer_name || 'Semua Customer'} diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 9119e80d..f0b5850c 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -47,6 +47,8 @@ const CustomerPaymentTab = () => { const [filterCustomer, setFilterCustomer] = useState( [] ); + // TODO: Uncomment when BE is ready + // const [filterSales, setFilterSales] = useState([]); const [filterSales, setFilterSales] = useState([]); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); @@ -60,6 +62,7 @@ const CustomerPaymentTab = () => { hasMore: hasMoreCustomers, } = useSelect(CustomerApi.basePath, 'id', 'name', 'search'); + // TODO: Uncomment when BE is ready const { options: salesOptions, isLoadingOptions: isLoadingSales, @@ -133,23 +136,18 @@ const CustomerPaymentTab = () => { count += 1; } - // Sales filter - if (filterSales.length > 0) { - count += 1; - } - - // Filter by (always count if submitted) - if (isSubmitted) { - count += 1; - } + // TODO: Uncomment when BE is ready + // // Sales filter + // if (filterSales.length > 0) { + // count += 1; + // } return count; }, [ filterStartDate, filterEndDate, filterCustomer, - filterSales, - isSubmitted, + // filterSales, ]); const hasFilters = activeFiltersCount > 0; @@ -163,11 +161,12 @@ const CustomerPaymentTab = () => { filterCustomer.length > 0 ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, - sales_id: - filterSales.length > 0 - ? filterSales.map((v) => String(v.value)).join(',') - : undefined, - filter_by: 'do_date' as const, + // TODO: Uncomment when BE is ready + // sales_id: + // filterSales.length > 0 + // ? filterSales.map((v) => String(v.value)).join(',') + // : undefined, + // filter_by: 'do_date' as const, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, page: currentPage, @@ -180,8 +179,8 @@ const CustomerPaymentTab = () => { ([, params]) => FinanceApi.getCustomerPaymentReport( params.customer_id, - params.sales_id, - params.filter_by, + 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, params.end_date, params.page, @@ -206,11 +205,11 @@ const CustomerPaymentTab = () => { filterCustomer.length > 0 ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, - sales_id: - filterSales.length > 0 - ? filterSales.map((v) => String(v.value)).join(',') - : undefined, - filter_by: 'do_date' as const, + // TODO: Uncomment when BE is ready + // sales_id: + // filterSales.length > 0 + // ? filterSales.map((v) => String(v.value)).join(',') + // : undefined, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, limit: 100, @@ -219,8 +218,8 @@ const CustomerPaymentTab = () => { const response = await FinanceApi.getCustomerPaymentReport( params.customer_id, - params.sales_id, - params.filter_by, + 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, params.end_date, params.page, @@ -277,13 +276,15 @@ const CustomerPaymentTab = () => { filterCustomer.length > 0 ? filterCustomer.map((c) => c.label).join(', ') : undefined, - sales: - filterSales.length > 0 - ? filterSales.map((s) => s.label).join(', ') - : undefined, + // TODO: Uncomment when BE is ready + // sales: + // filterSales.length > 0 + // ? filterSales.map((s) => s.label).join(', ') + // : undefined, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, - filter_by: 'do_date', + // TODO: Uncomment when BE is ready + // filter_by: 'do_date' as const, }, }); toast.success('PDF berhasil dibuat dan diunduh.'); @@ -661,7 +662,8 @@ const CustomerPaymentTab = () => { />
-
+ {/* TODO: Uncomment when BE is ready */} + {/*
{ onMenuScrollToBottom={loadMoreSales} className={{ wrapper: 'w-full' }} /> -
+
*/} -
+ {/* TODO: Uncomment when BE is ready */} + {/*
{ isDisabled={true} className={{ wrapper: 'w-full' }} /> -
+
*/} {/* Action Buttons */} diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 3a564e32..81f23481 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -13,8 +13,11 @@ export class FinanceApiService extends BaseApiService< async getCustomerPaymentReport( customer_id?: string, + // TODO: Uncomment when BE is ready + // sales_id?: string, + // filter_by?: 'do_date', sales_id?: string, - filter_by?: 'do_date', + filter_by?: 'do_date' | undefined, start_date?: string, end_date?: string, page?: number, @@ -26,8 +29,9 @@ export class FinanceApiService extends BaseApiService< method: 'GET', params: { customer_id: customer_id, - sales_id: sales_id, - filter_by: filter_by, + // TODO: Uncomment when BE is ready + // sales_id: sales_id, + // filter_by: filter_by, start_date: start_date, end_date: end_date, page: page, From 08c28f4077949cb3233717ff441f2876a2957f27 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 15:14:33 +0700 Subject: [PATCH 04/47] refactor(FE): Rename payment fields and add initial balance row --- .../export/CustomerPaymentExportPDF.tsx | 28 ++--- .../export/CustomerPaymentExportXLSX.tsx | 20 ++-- .../report/finance/tab/CustomerPaymentTab.tsx | 112 +++++++++++++++--- src/types/api/report/customer-payment.d.ts | 20 ++-- 4 files changed, 129 insertions(+), 51 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 5a656e7a..d1092e22 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -347,13 +347,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - {item.do_date ? formatDate(item.do_date, 'DD MMM YY') : '-'} + {item.trans_date + ? formatDate(item.trans_date, 'DD MMM YY') + : '-'} - {item.realization_date - ? formatDate(item.realization_date, 'DD MMM YY') + {item.delivery_date + ? formatDate(item.delivery_date, 'DD MMM YY') : '-'} @@ -366,7 +368,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {item.reference || '-'} - {item.vehicle_plate || '-'} + {item.vehicle_numbers || '-'} {formatNumber(item.qty)} @@ -390,10 +392,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatNumber(item.ppn)}% - {formatCurrency(item.total)} + {formatCurrency(item.total_price)} - {formatCurrency(item.payment)} + {formatCurrency(item.payment_amount)} @@ -401,30 +403,28 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - {item.notes ? ( - {item.notes} - ) : ( + {item.status ? ( - {item.accounts_receivable === 0 - ? 'Lunas' - : 'Belum Lunas'} + {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'} + ) : ( + - )} {item.pickup_info || '-'} - {item.sales_marketing || '-'} + {item.sales_person || '-'} ))} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index d51aa3b7..ea7cfd77 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -24,17 +24,15 @@ export const generateCustomerPaymentExcel = ( const excelData: { [key: string]: string | number }[] = customerData.map( (item, index) => ({ No: index + 1, - 'Tanggal DO/Bayar': item.do_date - ? formatDate(item.do_date, 'DD MMM YYYY') + 'Tanggal DO/Bayar': item.trans_date + ? formatDate(item.trans_date, 'DD MMM YYYY') : '', - 'Tanggal Realisasi': item.realization_date - ? formatDate(item.realization_date, 'DD MMM YYYY') + 'Tanggal Realisasi': item.delivery_date + ? formatDate(item.delivery_date, 'DD MMM YYYY') : '', Aging: formatNumber(item.aging_day || 0), Referensi: item.reference || '', - 'Nomor Polisi': Array.isArray(item.vehicle_plate) - ? item.vehicle_plate.join(', ') - : '', + 'Nomor Polisi': item.vehicle_numbers || '', 'Ekor/Qty': formatNumber(item.qty || 0), 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), @@ -42,12 +40,12 @@ export const generateCustomerPaymentExcel = ( CN: formatCurrency(item.credit_note || 0), 'Harga Akhir': formatCurrency(item.final_price || 0), 'PPN (%)': formatNumber(item.ppn || 0), - Total: formatCurrency(item.total || 0), - Pembayaran: formatCurrency(item.payment || 0), + Total: formatCurrency(item.total_price || 0), + Pembayaran: formatCurrency(item.payment_amount || 0), 'Saldo Piutang': formatCurrency(item.accounts_receivable || 0), - Keterangan: item.notes || '', + Keterangan: item.status || '', Pengambilan: item.pickup_info || '', - 'Sales/Marketing': item.sales_marketing || '', + 'Sales/Marketing': item.sales_person || '', }) ); diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index f0b5850c..ef320c15 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -302,24 +302,37 @@ const CustomerPaymentTab = () => { { id: 'no', header: 'No', - cell: (props) => props.row.index + 1, + cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + return isInitialBalanceRow ? 'Saldo Awal' : props.row.index; + }, footer: () =>
Total
, }, { id: 'do_date_or_payment_date', header: 'Tanggal DO/Bayar', - accessorKey: 'do_date', + accessorKey: 'trans_date', cell: (props) => { - const value = props.row.original.do_date; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; + const value = props.row.original.trans_date; return formatDate(value, 'DD MMM YYYY'); }, }, { id: 'realization_date', header: 'Tanggal Realisasi', - accessorKey: 'realization_date', + accessorKey: 'delivery_date', cell: (props) => { - const value = props.row.original.realization_date; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; + const value = props.row.original.delivery_date; return formatDate(value, 'DD MMM YYYY'); }, }, @@ -328,6 +341,10 @@ const CustomerPaymentTab = () => { header: 'Aging', accessorKey: 'aging_day', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; const value = props.row.original.aging_day; return (
@@ -341,6 +358,10 @@ const CustomerPaymentTab = () => { header: 'Referensi', accessorKey: 'reference', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; const value = props.row.original.reference; return value || '-'; }, @@ -348,9 +369,13 @@ const CustomerPaymentTab = () => { { id: 'vehicle_plate', header: 'Nomor Polisi', - accessorKey: 'vehicle_plate', + accessorKey: 'vehicle_numbers', cell: (props) => { - const value = props.row.original.vehicle_plate; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; + const value = props.row.original.vehicle_numbers; return value || '-'; }, }, @@ -359,6 +384,10 @@ const CustomerPaymentTab = () => { header: 'Ekor/Qty', accessorKey: 'qty', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.qty; return
{formatNumber(value)}
; }, @@ -373,6 +402,10 @@ const CustomerPaymentTab = () => { header: 'Berat (Kg)', accessorKey: 'weight', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.weight; return
{formatNumber(value)}
; }, @@ -387,6 +420,10 @@ const CustomerPaymentTab = () => { header: 'AVG', accessorKey: 'average_weight', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.average_weight; return
{formatNumber(value)}
; }, @@ -399,6 +436,10 @@ const CustomerPaymentTab = () => { header: 'Harga Awal', accessorKey: 'price', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.price; return
{formatCurrency(value)}
; }, @@ -413,6 +454,10 @@ const CustomerPaymentTab = () => { header: 'CN', accessorKey: 'credit_note', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.credit_note; return
{formatCurrency(value)}
; }, @@ -427,6 +472,10 @@ const CustomerPaymentTab = () => { header: 'Harga Akhir', accessorKey: 'final_price', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.final_price; return
{formatCurrency(value)}
; }, @@ -441,6 +490,10 @@ const CustomerPaymentTab = () => { header: 'PPN (%)', accessorKey: 'ppn', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; const value = props.row.original.ppn; return
{formatNumber(value)}%
; }, @@ -451,9 +504,13 @@ const CustomerPaymentTab = () => { { id: 'total', header: 'Total', - accessorKey: 'total', + accessorKey: 'total_price', cell: (props) => { - const value = props.row.original.total; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; + const value = props.row.original.total_price; return
{formatCurrency(value)}
; }, footer: () => ( @@ -465,9 +522,13 @@ const CustomerPaymentTab = () => { { id: 'payment', header: 'Pembayaran', - accessorKey: 'payment', + accessorKey: 'payment_amount', cell: (props) => { - const value = props.row.original.payment; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return
-
; + const value = props.row.original.payment_amount; return
{formatCurrency(value)}
; }, footer: () => ( @@ -495,9 +556,13 @@ const CustomerPaymentTab = () => { { id: 'notes', header: 'Keterangan', - accessorKey: 'notes', + accessorKey: 'status', cell: (props) => { - const value = props.row.original.notes; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; + const value = props.row.original.status; if (!value) { return '-'; @@ -522,6 +587,10 @@ const CustomerPaymentTab = () => { header: 'Pengambilan', accessorKey: 'pickup_info', cell: (props) => { + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; const value = props.row.original.pickup_info; return value || '-'; }, @@ -529,9 +598,13 @@ const CustomerPaymentTab = () => { { id: 'sales_marketing', header: 'Sales/Marketing', - accessorKey: 'sales_marketing', + accessorKey: 'sales_person', cell: (props) => { - const value = props.row.original.sales_marketing; + const isInitialBalanceRow = + Object.keys(props.row.original).length === 1 && + 'accounts_receivable' in props.row.original; + if (isInitialBalanceRow) return '-'; + const value = props.row.original.sales_person; return value || '-'; }, }, @@ -754,9 +827,14 @@ const CustomerPaymentTab = () => { collapsible={true} > 0} className={{ containerClassName: 'w-full', diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index bfa059c9..8c648cb9 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -2,12 +2,12 @@ import { BaseCustomer } from '@/types/api/master-data/customer'; import { BaseMetadata } from '@/types/api/api-general'; export type CustomerPaymentRow = { - id: number; - do_date: string; - realization_date: string; - aging_day: number | null; + transaction_type: string; + transaction_id: number; + trans_date: string; + delivery_date: string | null; reference: string; - vehicle_plate: string[]; + vehicle_numbers: string; qty: number; weight: number; average_weight: number; @@ -15,12 +15,13 @@ export type CustomerPaymentRow = { credit_note: number; final_price: number; ppn: number; - total: number; - payment: number; + total_price: number; + payment_amount: number; accounts_receivable: number; - notes: string; + aging_day: number; + status: string; pickup_info: string; - sales_marketing: string; + sales_person: string; }; export type CustomerPaymentSummary = { @@ -37,6 +38,7 @@ export type CustomerPaymentSummary = { export type CustomerPaymentReport = BaseMetadata & { customer: BaseCustomer; + initial_balance: number; rows: CustomerPaymentRow[]; summary: CustomerPaymentSummary; }; From ab2175d903b89e69140e8d987d9368e66037ca56 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 15:46:03 +0700 Subject: [PATCH 05/47] refactor(FE): Simplify table cells and render initial balance row --- .../report/finance/tab/CustomerPaymentTab.tsx | 121 +++++++----------- 1 file changed, 45 insertions(+), 76 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index ef320c15..d60d740a 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -302,49 +302,35 @@ const CustomerPaymentTab = () => { { id: 'no', header: 'No', - cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - return isInitialBalanceRow ? 'Saldo Awal' : props.row.index; - }, + cell: (props) => props.row.index, footer: () =>
Total
, }, { id: 'do_date_or_payment_date', header: 'Tanggal DO/Bayar', accessorKey: 'trans_date', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.trans_date; - return formatDate(value, 'DD MMM YYYY'); + return value ? formatDate(value, 'DD MMM YYYY') : '-'; }, }, { id: 'realization_date', header: 'Tanggal Realisasi', accessorKey: 'delivery_date', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.delivery_date; - return formatDate(value, 'DD MMM YYYY'); + return value ? formatDate(value, 'DD MMM YYYY') : '-'; }, }, { id: 'aging', header: 'Aging', accessorKey: 'aging_day', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.aging_day; return (
@@ -357,11 +343,8 @@ const CustomerPaymentTab = () => { id: 'reference', header: 'Referensi', accessorKey: 'reference', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.reference; return value || '-'; }, @@ -370,11 +353,8 @@ const CustomerPaymentTab = () => { id: 'vehicle_plate', header: 'Nomor Polisi', accessorKey: 'vehicle_numbers', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.vehicle_numbers; return value || '-'; }, @@ -383,11 +363,8 @@ const CustomerPaymentTab = () => { id: 'qty', header: 'Ekor/Qty', accessorKey: 'qty', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.qty; return
{formatNumber(value)}
; }, @@ -401,11 +378,8 @@ const CustomerPaymentTab = () => { id: 'weight', header: 'Berat (Kg)', accessorKey: 'weight', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.weight; return
{formatNumber(value)}
; }, @@ -419,11 +393,8 @@ const CustomerPaymentTab = () => { id: 'average_weight', header: 'AVG', accessorKey: 'average_weight', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.average_weight; return
{formatNumber(value)}
; }, @@ -435,11 +406,8 @@ const CustomerPaymentTab = () => { id: 'price', header: 'Harga Awal', accessorKey: 'price', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.price; return
{formatCurrency(value)}
; }, @@ -453,11 +421,8 @@ const CustomerPaymentTab = () => { id: 'credit_note', header: 'CN', accessorKey: 'credit_note', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.credit_note; return
{formatCurrency(value)}
; }, @@ -471,11 +436,8 @@ const CustomerPaymentTab = () => { id: 'final_price', header: 'Harga Akhir', accessorKey: 'final_price', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.final_price; return
{formatCurrency(value)}
; }, @@ -489,11 +451,8 @@ const CustomerPaymentTab = () => { id: 'ppn', header: 'PPN (%)', accessorKey: 'ppn', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.ppn; return
{formatNumber(value)}%
; }, @@ -505,11 +464,8 @@ const CustomerPaymentTab = () => { id: 'total', header: 'Total', accessorKey: 'total_price', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.total_price; return
{formatCurrency(value)}
; }, @@ -523,11 +479,8 @@ const CustomerPaymentTab = () => { id: 'payment', header: 'Pembayaran', accessorKey: 'payment_amount', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return
-
; const value = props.row.original.payment_amount; return
{formatCurrency(value)}
; }, @@ -541,6 +494,7 @@ const CustomerPaymentTab = () => { id: 'accounts_receivable', header: 'Saldo Piutang', accessorKey: 'accounts_receivable', + enableSorting: false, cell: (props) => { const value = props.row.original.accounts_receivable; return ( @@ -557,11 +511,8 @@ const CustomerPaymentTab = () => { id: 'notes', header: 'Keterangan', accessorKey: 'status', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.status; if (!value) { @@ -586,11 +537,8 @@ const CustomerPaymentTab = () => { id: 'pickup_info', header: 'Pengambilan', accessorKey: 'pickup_info', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.pickup_info; return value || '-'; }, @@ -599,11 +547,8 @@ const CustomerPaymentTab = () => { id: 'sales_marketing', header: 'Sales/Marketing', accessorKey: 'sales_person', + enableSorting: false, cell: (props) => { - const isInitialBalanceRow = - Object.keys(props.row.original).length === 1 && - 'accounts_receivable' in props.row.original; - if (isInitialBalanceRow) return '-'; const value = props.row.original.sales_person; return value || '-'; }, @@ -854,6 +799,30 @@ const CustomerPaymentTab = () => { 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', paginationClassName: 'hidden', }} + renderCustomRow={(row) => { + if (row.index === 0) { + return ( +
+ + + + + ); + } + }} /> ); From c5baff6f335a4b43182583d187ee4a5612805dd8 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 15:55:20 +0700 Subject: [PATCH 06/47] refactor(FE): Update column headers in CustomerPaymentTab --- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index d60d740a..8e642097 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -307,7 +307,7 @@ const CustomerPaymentTab = () => { }, { id: 'do_date_or_payment_date', - header: 'Tanggal DO/Bayar', + header: 'Tanggal Jual/Bayar', accessorKey: 'trans_date', enableSorting: false, cell: (props) => { @@ -361,7 +361,7 @@ const CustomerPaymentTab = () => { }, { id: 'qty', - header: 'Ekor/Qty', + header: 'Qty', accessorKey: 'qty', enableSorting: false, cell: (props) => { From e134f0994bafc0959c208c661920caff3e665dbc Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 16:57:52 +0700 Subject: [PATCH 07/47] refactor(FE): Remove credit_note and ppn fields --- .../export/CustomerPaymentExportPDF.tsx | 11 -------- .../export/CustomerPaymentExportXLSX.tsx | 4 --- .../report/finance/tab/CustomerPaymentTab.tsx | 28 ------------------- src/types/api/report/customer-payment.d.ts | 4 --- 4 files changed, 47 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index d1092e22..8887320c 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -382,15 +382,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatCurrency(item.price)} - - {formatCurrency(item.credit_note)} - {formatCurrency(item.final_price)} - - {formatNumber(item.ppn)}% - {formatCurrency(item.total_price)} @@ -468,11 +462,6 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { )} - - - {formatCurrency(customerReport.summary.total_credit_note)} - - {formatCurrency(customerReport.summary.total_final_amount)} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index ea7cfd77..2fe29c76 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -37,9 +37,7 @@ export const generateCustomerPaymentExcel = ( 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), 'Harga Awal': formatCurrency(item.price || 0), - CN: formatCurrency(item.credit_note || 0), 'Harga Akhir': formatCurrency(item.final_price || 0), - 'PPN (%)': formatNumber(item.ppn || 0), Total: formatCurrency(item.total_price || 0), Pembayaran: formatCurrency(item.payment_amount || 0), 'Saldo Piutang': formatCurrency(item.accounts_receivable || 0), @@ -63,11 +61,9 @@ export const generateCustomerPaymentExcel = ( 'Harga Awal': formatCurrency( customerReport.summary.total_initial_amount || 0 ), - CN: formatCurrency(customerReport.summary.total_credit_note || 0), 'Harga Akhir': formatCurrency( customerReport.summary.total_final_amount || 0 ), - 'PPN (%)': '', Total: formatCurrency(customerReport.summary.total_grand_amount || 0), Pembayaran: formatCurrency(customerReport.summary.total_payment || 0), 'Saldo Piutang': formatCurrency( diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 8e642097..8b2debbf 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -417,21 +417,6 @@ const CustomerPaymentTab = () => { ), }, - { - id: 'credit_note', - header: 'CN', - accessorKey: 'credit_note', - enableSorting: false, - cell: (props) => { - const value = props.row.original.credit_note; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(summary.total_credit_note) || '-'} -
- ), - }, { id: 'final_price', header: 'Harga Akhir', @@ -447,19 +432,6 @@ const CustomerPaymentTab = () => { ), }, - { - id: 'ppn', - header: 'PPN (%)', - accessorKey: 'ppn', - enableSorting: false, - cell: (props) => { - const value = props.row.original.ppn; - return
{formatNumber(value)}%
; - }, - footer: () => ( -
-
- ), - }, { id: 'total', header: 'Total', diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index 8c648cb9..f25f510a 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -12,9 +12,7 @@ export type CustomerPaymentRow = { weight: number; average_weight: number; price: number; - credit_note: number; final_price: number; - ppn: number; total_price: number; payment_amount: number; accounts_receivable: number; @@ -28,9 +26,7 @@ export type CustomerPaymentSummary = { total_qty: number; total_weight: number; total_initial_amount: number; - total_credit_note: number; total_final_amount: number; - total_ppn: number; total_grand_amount: number; total_payment: number; total_accounts_receivable: number; From 916de1432b6b39ce2cb3170ff70fbc7d35dc1952 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 16:59:45 +0700 Subject: [PATCH 08/47] refactor(FE): Adjust column span in CustomerPaymentTab --- 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 8b2debbf..7d34a08f 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -780,7 +780,7 @@ const CustomerPaymentTab = () => { >
- From 4137683d05628e8278b37eb832ba860ce9768b48 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 17:55:38 +0700 Subject: [PATCH 10/47] refactor(FE): Hide 'hari' for zero or negative aging days --- 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 951bc8f1..c5e79d8b 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -334,7 +334,7 @@ const CustomerPaymentTab = () => { const value = props.row.original.aging_day; return (
- {value ? formatNumber(value) : '-'} hari + {value && value > 0 ? `${formatNumber(value)} hari` : '-'}
); }, From b290f7692a2213aa08d69f30a8e4d736bcdcc42c Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 21:39:38 +0700 Subject: [PATCH 11/47] refactor(FE): Show current chick total in recording form --- .../recording/form/RecordingForm.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 4f9018fc..66164faa 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -570,6 +570,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return recordedIds; }, [existingRecordings, today]); + const currentTotalChickQty = useMemo(() => { + if (!isResponseSuccess(existingRecordings) || !selectedKandang) return null; + + let projectFlockKandangId: number | undefined; + + if (projectFlockKandangLookup) { + projectFlockKandangId = + projectFlockKandangLookup.project_flock_kandang_id; + } else if (projectFlockKandangDetail) { + projectFlockKandangId = projectFlockKandangDetail.id; + } + + if (!projectFlockKandangId) return null; + + const recording = existingRecordings.data.find( + (rec) => + rec.project_flock.project_flock_kandang_id === projectFlockKandangId + ); + + return recording?.project_flock.total_chick_qty || null; + }, [ + existingRecordings, + selectedKandang, + projectFlockKandangLookup, + projectFlockKandangDetail, + ]); + const unifiedStockProducts = useMemo(() => { const options: OptionType[] = []; if (isResponseSuccess(stockProducts) && selectedKandang) { @@ -1436,6 +1463,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? getProjectFlockBadgeAdornment() : undefined } + bottomLabel={ + currentTotalChickQty + ? `Jumlah ayam saat ini: ${formatNumber( + currentTotalChickQty + )}` + : undefined + } /> From dbe9b268189b43653f203f5f6e61f1a661036e0e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 21:42:36 +0700 Subject: [PATCH 12/47] feat(FE): Adjust recording form grid and add chick count --- .../production/recording/form/RecordingForm.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 66164faa..dec106b1 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1487,7 +1487,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { body: 'flex flex-col gap-4', }} > -
+
{initialValues.approval && (
@@ -1575,6 +1575,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )}

+
+ + Jumlah Ayam Saat Ini + +

+ {initialValues.project_flock?.total_chick_qty + ? formatNumber( + initialValues.project_flock.total_chick_qty + ) + : '-'} +

+
Hari

Hari ke-{initialValues.day}

From 359326e575c87b5dfaa24594ac064d3bb6d415c3 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 21:45:52 +0700 Subject: [PATCH 13/47] refactor(FE): Rename 'Populasi Awal' column to 'Populasi Ayam' --- src/components/pages/production/recording/RecordingTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index a99cfbbc..96ac52ed 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -715,7 +715,7 @@ const RecordingTable = () => { formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), }, { - header: 'Populasi Awal', + header: 'Populasi Ayam', cell: (props) => props.row.original.project_flock?.total_chick_qty?.toLocaleString() || '-', From d8e134d404478869b28da032b0ed2ecbc731263f Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 21:59:03 +0700 Subject: [PATCH 14/47] feat(FE): Add customer payment permission to finance route --- src/config/route-permission.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts index 9a0c9d2e..176b4385 100644 --- a/src/config/route-permission.ts +++ b/src/config/route-permission.ts @@ -121,6 +121,7 @@ export const ROUTE_PERMISSIONS: Record = { '/report/finance/': [ 'lti.repport.finance.list', 'lti.repport.debtsupplier.list', + 'lti.repport.customerpayment.list', ], // Inventory From a72fbec5ceab4997fb462b5e23dde8a30e3a0554 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 22:02:48 +0700 Subject: [PATCH 15/47] refactor(FE): Remove CN and PPN columns and bold receivables --- .../report/finance/export/CustomerPaymentExportPDF.tsx | 3 --- .../report/finance/export/CustomerPaymentExportXLSX.tsx | 2 -- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 7 +++++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 8887320c..8dce38ad 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -302,9 +302,6 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Harga Awal - - CN - Harga Akhir diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index 2fe29c76..b14b5ef6 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -88,9 +88,7 @@ export const generateCustomerPaymentExcel = ( { wch: 12 }, // Berat { wch: 10 }, // AVG { wch: 15 }, // Harga Awal - { wch: 10 }, // CN { wch: 15 }, // Harga Akhir - { wch: 10 }, // PPN { wch: 15 }, // Total { wch: 15 }, // Pembayaran { wch: 15 }, // Saldo Piutang diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index c5e79d8b..928c9c41 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -470,7 +470,11 @@ const CustomerPaymentTab = () => { cell: (props) => { const value = props.row.original.accounts_receivable; return ( -
+
{formatCurrency(value)}
); @@ -728,7 +732,6 @@ const CustomerPaymentTab = () => { total_initial_amount: 0, total_credit_note: 0, total_final_amount: 0, - total_ppn: 0, total_grand_amount: 0, total_payment: 0, total_accounts_receivable: 0, From f1dba4012aed8dfef95771863f937f1683451e72 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 22:17:24 +0700 Subject: [PATCH 16/47] refactor(FE): Update customer payment types and exports --- .../export/CustomerPaymentExportPDF.tsx | 18 ++++-------------- .../export/CustomerPaymentExportXLSX.tsx | 12 +++++++----- .../report/finance/tab/CustomerPaymentTab.tsx | 8 ++------ src/types/api/report/customer-payment.d.ts | 7 +++---- 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 8dce38ad..71969e8a 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -284,7 +284,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Aging - + Referensi @@ -305,9 +305,6 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Harga Akhir - - Pajak - Total @@ -361,7 +358,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {item.aging_day ? formatNumber(item.aging_day) : '-'} hari - + {item.reference || '-'} @@ -435,7 +432,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - + @@ -453,20 +450,13 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - - {formatCurrency( - customerReport.summary.total_initial_amount - )} - + {formatCurrency(customerReport.summary.total_final_amount)} - - - {formatCurrency(customerReport.summary.total_grand_amount)} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index b14b5ef6..3fb21488 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -32,7 +32,9 @@ export const generateCustomerPaymentExcel = ( : '', Aging: formatNumber(item.aging_day || 0), Referensi: item.reference || '', - 'Nomor Polisi': item.vehicle_numbers || '', + 'Nomor Polisi': Array.isArray(item.vehicle_numbers) + ? item.vehicle_numbers.join(', ') + : '', 'Ekor/Qty': formatNumber(item.qty || 0), 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), @@ -42,7 +44,9 @@ export const generateCustomerPaymentExcel = ( Pembayaran: formatCurrency(item.payment_amount || 0), 'Saldo Piutang': formatCurrency(item.accounts_receivable || 0), Keterangan: item.status || '', - Pengambilan: item.pickup_info || '', + Pengambilan: Array.isArray(item.pickup_info) + ? item.pickup_info.join(', ') + : '', 'Sales/Marketing': item.sales_person || '', }) ); @@ -58,9 +62,7 @@ export const generateCustomerPaymentExcel = ( 'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0), 'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0), AVG: '', - 'Harga Awal': formatCurrency( - customerReport.summary.total_initial_amount || 0 - ), + 'Harga Awal': '', 'Harga Akhir': formatCurrency( customerReport.summary.total_final_amount || 0 ), diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 928c9c41..18a8674a 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -412,9 +412,7 @@ const CustomerPaymentTab = () => { return
{formatCurrency(value)}
; }, footer: () => ( -
- {formatCurrency(summary.total_initial_amount) || '-'} -
+
-
), }, { @@ -510,7 +508,7 @@ const CustomerPaymentTab = () => { status: getPaymentStatusIndicatorColor(value), }} > - {getPaymentStatusText(value)} + {getPaymentStatusText(value)} ); }, @@ -729,8 +727,6 @@ const CustomerPaymentTab = () => { const summary = customerReport.summary || { total_qty: 0, total_weight: 0, - total_initial_amount: 0, - total_credit_note: 0, total_final_amount: 0, total_grand_amount: 0, total_payment: 0, diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index f25f510a..9169c99b 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -7,7 +7,7 @@ export type CustomerPaymentRow = { trans_date: string; delivery_date: string | null; reference: string; - vehicle_numbers: string; + vehicle_numbers: string[]; qty: number; weight: number; average_weight: number; @@ -16,16 +16,15 @@ export type CustomerPaymentRow = { total_price: number; payment_amount: number; accounts_receivable: number; - aging_day: number; + aging_day: number | null; status: string; - pickup_info: string; + pickup_info: string[]; sales_person: string; }; export type CustomerPaymentSummary = { total_qty: number; total_weight: number; - total_initial_amount: number; total_final_amount: number; total_grand_amount: number; total_payment: number; From 427c8aec341048f4ce95f12896d81ea303cc9002 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 22:28:45 +0700 Subject: [PATCH 17/47] feat(FE): Join array fields into comma-separated strings --- .../finance/export/CustomerPaymentExportPDF.tsx | 12 ++++++++++-- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 71969e8a..9b1fd640 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -362,7 +362,11 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {item.reference || '-'}
- {item.vehicle_numbers || '-'} + + {Array.isArray(item.vehicle_numbers) + ? item.vehicle_numbers.join(', ') + : item.vehicle_numbers || '-'} + {formatNumber(item.qty)} @@ -409,7 +413,11 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { )} - {item.pickup_info || '-'} + + {Array.isArray(item.pickup_info) + ? item.pickup_info.join(', ') + : item.pickup_info || '-'} + {item.sales_person || '-'} diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 18a8674a..78d84800 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -356,7 +356,7 @@ const CustomerPaymentTab = () => { enableSorting: false, cell: (props) => { const value = props.row.original.vehicle_numbers; - return value || '-'; + return Array.isArray(value) ? value.join(', ') : value || '-'; }, }, { @@ -520,7 +520,7 @@ const CustomerPaymentTab = () => { enableSorting: false, cell: (props) => { const value = props.row.original.pickup_info; - return value || '-'; + return Array.isArray(value) ? value.join(', ') : value || '-'; }, }, { From cf332b534687d8475190bdc827e870c100f43476 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 10:00:56 +0700 Subject: [PATCH 18/47] refactor(FE): Add load-on-scroll for expedition vendor select --- .../purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx index 2b18afee..2f619778 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx @@ -156,6 +156,8 @@ const PurchaseOrderAcceptApprovalForm = ({ setInputValue: setExpeditionsSelectInputValue, options: expeditionVendors, isLoadingOptions: isLoadingExpeditions, + loadMore: loadMoreExpeditions, + hasMore: hasMoreExpeditions, } = useSelect(SupplierApi.basePath, 'id', 'name', 'search', { category: 'BOP', }); @@ -570,6 +572,8 @@ const PurchaseOrderAcceptApprovalForm = ({ expeditionVendorChangeHandler(idx, val) } options={getExpeditionVendorOptions()} + isLoading={isLoadingExpeditions} + onMenuScrollToBottom={loadMoreExpeditions} isError={ isRepeaterInputError(idx, 'expedition_vendor_id') .isError From 4b88b684af451bcc4aa3e35fd9454fab669c1a84 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 10:12:52 +0700 Subject: [PATCH 19/47] refactor(FE): Load locations by area and disable location select --- .../form/request/PurchaseRequestForm.tsx | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx index 0c319f5a..adab2fbe 100644 --- a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx +++ b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx @@ -63,6 +63,10 @@ const PurchaseRequestForm = ({ useState(''); const [formErrorList, setFormErrorList] = useState([]); + const [selectedArea, setSelectedArea] = useState(''); + const [selectedLocation, setSelectedLocation] = useState(''); + const [disabledLocation, setDisabledLocation] = useState(true); + // ===== TYPE DEFINITIONS ===== interface ProductOptionType { value: number; @@ -160,6 +164,18 @@ const PurchaseRequestForm = ({ isLoadingOptions: isLoadingAreas, } = useSelect(AreaApi.basePath, 'id', 'name', 'search'); + const { + options: locationOptions, + isLoadingOptions: isLoadingLocations, + loadMore: loadMoreLocations, + hasMore: hasMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', '', { + area_id: + selectedArea != '' + ? selectedArea + : ((initialValues?.area?.id ?? '') as string), + }); + const { inputValue: warehouseSelectInputValue, setInputValue: setWarehouseSelectInputValue, @@ -267,31 +283,6 @@ const PurchaseRequestForm = ({ return data; }, [supplierData]); - const locationsUrl = useMemo(() => { - const params = new URLSearchParams({ - search: locationSelectInputValue, - ...(formik.values.area_id && formik.values.area_id > 0 - ? { area_id: formik.values.area_id.toString() } - : {}), - }); - return `${LocationApi.basePath}?${params.toString()}`; - }, [locationSelectInputValue, formik.values.area_id]); - - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationsUrl, - LocationApi.getAllFetcher - ); - - const locationOptions = useMemo(() => { - if (!isResponseSuccess(locations)) return []; - return ( - locations?.data.map((location) => ({ - value: location.id, - label: location.name, - })) || [] - ); - }, [locations]); - const warehousesUrl = useMemo(() => { const params = new URLSearchParams({ search: warehouseSelectInputValue }); @@ -407,6 +398,18 @@ const PurchaseRequestForm = ({ } }, [formik.values.supplier_id]); + useEffect(() => { + if (type !== 'add' && initialValues) { + if (initialValues.area?.id) { + setSelectedArea(initialValues.area.id.toString()); + setDisabledLocation(false); + } + if (initialValues.location?.id) { + setSelectedLocation(initialValues.location.id.toString()); + } + } + }, [type, initialValues]); + // ===== FORM HANDLERS ===== const handleSupplierChange = useCallback( (val: OptionType | OptionType[] | null) => { @@ -445,6 +448,16 @@ const PurchaseRequestForm = ({ formik.setFieldValue('area_id', (area as OptionType)?.value || 0); formik.setFieldTouched('area', true); formik.setFieldValue('area', area); + + setSelectedArea((area as OptionType)?.value as string); + setSelectedLocation(''); + const disabled = (area as OptionType)?.value == null; + setDisabledLocation(disabled); + + formik.setFieldTouched('location_id', false); + formik.setFieldValue('location_id', 0); + formik.setFieldTouched('location', false); + formik.setFieldValue('location', null); }, [] ); @@ -456,6 +469,8 @@ const PurchaseRequestForm = ({ formik.setFieldValue('location_id', (location as OptionType)?.value || 0); formik.setFieldTouched('location', true); formik.setFieldValue('location', location); + + setSelectedLocation((location as OptionType)?.value as string); }, [] ); @@ -596,10 +611,15 @@ const PurchaseRequestForm = ({ placeholder='Pilih Lokasi...' value={formik.values.location} onChange={handleLocationChange} - options={locationOptions} + options={ + selectedArea != '' || initialValues?.area?.id + ? locationOptions + : [] + } onInputChange={setLocationSelectInputValue} isLoading={isLoadingLocations} - isDisabled={type === 'detail'} + onMenuScrollToBottom={loadMoreLocations} + isDisabled={type === 'detail' || disabledLocation} isClearable={type !== 'detail'} /> From 3bc5030a3d3df1297886147199e3a7b26d2b1ce9 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 10:17:08 +0700 Subject: [PATCH 20/47] refactor(FE): Use useSelect for warehouse options --- .../form/request/PurchaseRequestForm.tsx | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx index adab2fbe..9a54d537 100644 --- a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx +++ b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx @@ -67,12 +67,6 @@ const PurchaseRequestForm = ({ const [selectedLocation, setSelectedLocation] = useState(''); const [disabledLocation, setDisabledLocation] = useState(true); - // ===== TYPE DEFINITIONS ===== - interface ProductOptionType { - value: number; - label: string; - } - // ===== UTILITY FUNCTIONS ===== const isRepeaterInputError = ( idx: number, @@ -179,8 +173,20 @@ const PurchaseRequestForm = ({ const { inputValue: warehouseSelectInputValue, setInputValue: setWarehouseSelectInputValue, + options: warehouseOptions, isLoadingOptions: isLoadingWarehouses, - } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search'); + loadMore: loadMoreWarehouses, + hasMore: hasMoreWarehouses, + } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search', { + area_id: + selectedArea != '' + ? selectedArea + : ((initialValues?.area?.id ?? '') as string), + location_id: + selectedLocation != '' + ? selectedLocation + : ((initialValues?.location?.id ?? '') as string), + }); // ===== FORM CONFIGURATION ===== const formikInitialValues = useMemo( @@ -283,45 +289,6 @@ const PurchaseRequestForm = ({ return data; }, [supplierData]); - const warehousesUrl = useMemo(() => { - const params = new URLSearchParams({ search: warehouseSelectInputValue }); - - if (formik.values.area_id && formik.values.area_id > 0) { - params.append('area_id', formik.values.area_id.toString()); - } - - if (formik.values.location_id && formik.values.location_id > 0) { - params.append('location_id', formik.values.location_id.toString()); - } - - return `${WarehouseApi.basePath}?${params.toString()}`; - }, [ - warehouseSelectInputValue, - formik.values.area_id, - formik.values.location_id, - ]); - - const { data: warehouses } = useSWR( - warehousesUrl, - WarehouseApi.getAllFetcher - ); - - const warehouseOptions = useMemo(() => { - if (!isResponseSuccess(warehouses)) return []; - - return ( - warehouses?.data.map((w) => ({ - value: w.id, - label: w.name, - area: w.area?.name, - location: - 'type' in w && (w.type === 'LOKASI' || w.type === 'KANDANG') - ? w.location?.name - : undefined, - })) || [] - ); - }, [warehouses]); - const addPurchaseItem = () => { const newItems = [ ...(formik.values.items || []), @@ -733,6 +700,7 @@ const PurchaseRequestForm = ({ options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} isLoading={isLoadingWarehouses} + onMenuScrollToBottom={loadMoreWarehouses} isError={ isRepeaterInputError(idx, 'warehouse_id').isError } @@ -752,9 +720,9 @@ const PurchaseRequestForm = ({ required value={item.product ?? undefined} onChange={(val) => { - const product = val as ProductOptionType | null; + const product = val as OptionType | null; const productId = - (product as ProductOptionType)?.value || 0; + (product as OptionType)?.value || 0; formik.setFieldTouched( `items.${idx}.product`, From a1301121ac9a9dc12db15a644cc85647d7be8ba4 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 10:44:27 +0700 Subject: [PATCH 21/47] refactor(FE): Refactor selects to use useSelect hook --- .../recording/form/RecordingForm.tsx | 299 ++++++++---------- 1 file changed, 129 insertions(+), 170 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index dec106b1..f492c987 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -12,7 +12,10 @@ import RequirePermission from '@/components/helper/RequirePermission'; import Card from '@/components/Card'; import Badge from '@/components/Badge'; import NumberInput from '@/components/input/NumberInput'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; import CheckboxInput from '@/components/input/CheckboxInput'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; @@ -26,6 +29,7 @@ import { } from '@/services/api/production'; import { LocationApi } from '@/services/api/master-data'; import { ProductWarehouseApi } from '@/services/api/inventory'; +import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { CreateGrowingRecordingPayload, @@ -36,7 +40,10 @@ import { NextDayRecording, } from '@/types/api/production/recording'; import { type BaseApiResponse } from '@/types/api/api-general'; -import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; +import { + ProjectFlockKandangLookup, + ProjectFlock, +} from '@/types/api/production/project-flock'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Kandang } from '@/types/api/master-data/kandang'; @@ -77,16 +84,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [selectedDepletions, setSelectedDepletions] = useState([]); const [selectedEggs, setSelectedEggs] = useState([]); - const [locationSearchValue, setLocationSearchValue] = useState(''); const [selectedLocation, setSelectedLocation] = useState( null ); - const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); const [selectedProjectFlock, setSelectedProjectFlock] = useState(null); const [selectedKandang, setSelectedKandang] = useState( null ); + const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] = + useState(''); + const [stockProductsLocationId, setStockProductsLocationId] = + useState(''); + const [stockProductsKandangId, setStockProductsKandangId] = + useState(''); + const [depletionProductsLocationId, setDepletionProductsLocationId] = + useState(''); + const [depletionProductsKandangId, setDepletionProductsKandangId] = + useState(''); + const [eggProductsLocationId, setEggProductsLocationId] = + useState(''); + const [eggProductsKandangId, setEggProductsKandangId] = useState(''); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); @@ -210,26 +228,24 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [deleteModal, initialValues?.id, router]); // ===== API DATA FETCHING ===== - const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({ - search: locationSearchValue || '', - limit: '100', - }).toString()}`; - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationsUrl, - LocationApi.getAllFetcher - ); + const { + setInputValue: setLocationSearchValue, + options: locationOptions, + isLoadingOptions: isLoadingLocations, + loadMore: loadMoreLocations, + hasMore: hasMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); - const projectFlocksUrl = `${ProjectFlockApi.basePath}?${new URLSearchParams({ - search: projectFlockSearchValue || '', - limit: '100', - ...(selectedLocation - ? { location_id: selectedLocation.value.toString() } - : {}), - }).toString()}`; - const { data: projectFlocks, isLoading: isLoadingProjectFlocks } = useSWR( - projectFlocksUrl, - ProjectFlockApi.getAllFetcher - ); + const { + setInputValue: setProjectFlockSearchValue, + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlocks, + loadMore: loadMoreProjectFlocks, + hasMore: hasMoreProjectFlocks, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { + location_id: selectedProjectFlockLocationId, + }); const projectFlockKandangLookupUrl = useMemo(() => { if (!selectedProjectFlock || !selectedKandang) return null; @@ -279,46 +295,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangDetailData.data : undefined; - const stockProductsUrl = useMemo(() => { - if (!selectedLocation || !selectedKandang) return null; - const params = new URLSearchParams({ - flags: 'PAKAN,OVK', - search: '', - limit: '100', - location_id: selectedLocation.value.toString(), - }); + const { + options: stockProductOptions, + rawData: stockProducts, + isLoadingOptions: isLoadingStockProducts, + loadMore: loadMoreStockProducts, + hasMore: hasMoreStockProducts, + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { + flags: 'PAKAN,OVK', + location_id: stockProductsLocationId, + kandang_id: stockProductsKandangId, + }); - if (projectFlockKandangLookup?.kandang?.id) { - params.append( - 'kandang_id', - projectFlockKandangLookup.kandang.id.toString() - ); - } else if (selectedKandang) { - params.append('kandang_id', selectedKandang.value.toString()); - } - - return `${ProductWarehouseApi.basePath}?${params.toString()}`; - }, [selectedLocation, selectedKandang, projectFlockKandangLookup]); - - const depletionProductsUrl = useMemo(() => { - if (!selectedLocation || !selectedKandang) return null; - const params = new URLSearchParams({ - search: '', - limit: '100', - location_id: selectedLocation.value.toString(), - }); - - if (projectFlockKandangLookup?.kandang?.id) { - params.append( - 'kandang_id', - projectFlockKandangLookup.kandang.id.toString() - ); - } else if (selectedKandang) { - params.append('kandang_id', selectedKandang.value.toString()); - } - - return `${ProductWarehouseApi.basePath}?${params.toString()}`; - }, [selectedLocation, selectedKandang, projectFlockKandangLookup]); + const { + options: depletionProductOptions, + rawData: depletionProductsData, + isLoadingOptions: isLoadingDepletionProducts, + loadMore: loadMoreDepletionProducts, + hasMore: hasMoreDepletionProducts, + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { + location_id: depletionProductsLocationId, + kandang_id: depletionProductsKandangId, + }); const today = new Date().toISOString().split('T')[0]; const existingRecordingsUrl = `${RecordingApi.basePath}`; @@ -360,38 +358,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } }, [nextDayRecordingData]); - const { data: stockProducts, isLoading: isLoadingStockProducts } = useSWR( - stockProductsUrl, - ProductWarehouseApi.getAllFetcher - ); - - const { data: depletionProductsData, isLoading: isLoadingDepletionProducts } = - useSWR(depletionProductsUrl, ProductWarehouseApi.getAllFetcher); - - const eggProductsUrl = useMemo(() => { - if (!selectedLocation || !selectedKandang) return null; - const params = new URLSearchParams({ - search: 'telur', - limit: '100', - location_id: selectedLocation.value.toString(), - }); - - if (projectFlockKandangLookup?.kandang?.id) { - params.append( - 'kandang_id', - projectFlockKandangLookup.kandang.id.toString() - ); - } else if (selectedKandang) { - params.append('kandang_id', selectedKandang.value.toString()); - } - - return `${ProductWarehouseApi.basePath}?${params.toString()}`; - }, [selectedLocation, selectedKandang, projectFlockKandangLookup]); - - const { data: eggProductsData, isLoading: isLoadingEggProducts } = useSWR( - eggProductsUrl, - ProductWarehouseApi.getAllFetcher - ); + const { + options: eggProductOptions, + rawData: eggProductsData, + isLoadingOptions: isLoadingEggProducts, + loadMore: loadMoreEggProducts, + hasMore: hasMoreEggProducts, + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { + search: 'telur', + location_id: eggProductsLocationId, + kandang_id: eggProductsKandangId, + }); const approvedProjectFlockKandangsUrl = useMemo(() => { const params = new URLSearchParams({ @@ -448,17 +425,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }); // ===== DATA PROCESSING ===== - const locationOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(locations)) { - const locationOptionsList = - locations?.data.map((location) => ({ - value: location.id, - label: location.name || '', - })) || []; - options = options.concat(locationOptionsList); - } + const enhancedLocationOptions = useMemo(() => { + const options = [...locationOptions]; if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) { const currentLocation = projectFlockKandangDetail.project_flock.location; @@ -474,19 +442,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [locations, projectFlockKandangDetail, type]); + }, [locationOptions, projectFlockKandangDetail, type]); - const projectFlockOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(projectFlocks)) { - const flockOptions = - projectFlocks?.data.map((projectFlock) => ({ - value: projectFlock.id, - label: projectFlock.flock_name || '', - })) || []; - options = options.concat(flockOptions); - } + const enhancedProjectFlockOptions = useMemo(() => { + const options = [...projectFlockOptions]; if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) { const currentProjectFlock = projectFlockKandangDetail.project_flock; @@ -502,13 +461,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [projectFlocks, projectFlockKandangDetail, type]); + }, [projectFlockOptions, projectFlockKandangDetail, type]); const kandangOptions = useMemo(() => { let options: OptionType[] = []; - if (selectedProjectFlock && isResponseSuccess(projectFlocks)) { - const selectedProjectFlockData = projectFlocks.data.find( + if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) { + const data = projectFlocksRawData.data as ProjectFlock[]; + const selectedProjectFlockData = data.find( (pf) => pf.id === selectedProjectFlock.value ); @@ -548,7 +508,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return options; }, [ selectedProjectFlock, - projectFlocks, + projectFlocksRawData, projectFlockKandangDetail, type, approvedProjectFlockKandangs, @@ -598,20 +558,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ]); const unifiedStockProducts = useMemo(() => { - const options: OptionType[] = []; - if (isResponseSuccess(stockProducts) && selectedKandang) { - stockProducts.data.forEach((product) => { - const hasPakanFlag = product.product.flags?.includes('PAKAN'); - const hasOvkFlag = product.product.flags?.includes('OVK'); - - if (hasPakanFlag || hasOvkFlag) { - options.push({ - value: product.id, - label: product.product.name, - }); - } - }); - } + const options = [...stockProductOptions]; if ( initialValues && @@ -635,12 +582,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [stockProducts, initialValues, type, selectedKandang]); + }, [stockProductOptions, initialValues, type]); const depletionProducts = useMemo(() => { const options: OptionType[] = []; + if (isResponseSuccess(depletionProductsData) && selectedKandang) { - depletionProductsData.data.forEach((product) => { + const data = depletionProductsData.data as unknown as ProductWarehouse[]; + data.forEach((product) => { const productName = product.product.name; if ( @@ -680,8 +629,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const eggProducts = useMemo(() => { const options: OptionType[] = []; + if (isResponseSuccess(eggProductsData) && selectedKandang) { - eggProductsData.data.forEach((product) => { + const data = eggProductsData.data as unknown as ProductWarehouse[]; + data.forEach((product) => { const productName = product.product.name; if ( @@ -812,33 +763,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; // ===== HELPER FUNCTIONS ===== - useCallback((): OptionType | null => { - if ( - !formik.values.project_flock_kandang || - !isResponseSuccess(projectFlocks) - ) { - return selectedLocation; - } - const projectFlockId = formik.values.project_flock_kandang.value; - const projectFlock = projectFlocks.data.find( - (pf) => pf.id === projectFlockId - ); - if (projectFlock && projectFlock.location) { - return { - value: projectFlock.location.id, - label: projectFlock.location.name, - }; - } - return selectedLocation; - }, [formik.values.project_flock_kandang, projectFlocks, selectedLocation]); - const getAvailableStock = useCallback( (productWarehouseId: number) => { if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0; if (!isResponseSuccess(stockProducts)) return 0; - const productWarehouse = stockProducts.data.find( - (pw) => pw.id === productWarehouseId - ); + const data = stockProducts.data as unknown as ProductWarehouse[]; + const productWarehouse = data.find((pw) => pw.id === productWarehouseId); return productWarehouse?.quantity ?? 0; }, [stockProducts, type] @@ -915,9 +845,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { (productWarehouseId: number) => { if (!isResponseSuccess(stockProducts)) return null; - const productWarehouse = stockProducts.data.find( - (pw) => pw.id === productWarehouseId - ); + const data = stockProducts.data as unknown as ProductWarehouse[]; + const productWarehouse = data.find((pw) => pw.id === productWarehouseId); if (!productWarehouse) return null; const hasPakanFlag = productWarehouse.product.flags?.includes('PAKAN'); @@ -1002,9 +931,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== EVENT HANDLERS ===== const locationChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedLocation(val as OptionType); + const location = val as OptionType; + setSelectedLocation(location); setSelectedProjectFlock(null); setSelectedKandang(null); + setSelectedProjectFlockLocationId( + location ? location.value.toString() : '' + ); formik.setFieldValue('project_flock_kandang', null); formik.setFieldValue('project_flock_kandang_id', 0); }; @@ -1017,7 +950,23 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedKandang(val as OptionType); + const kandang = val as OptionType; + setSelectedKandang(kandang); + if (selectedLocation && kandang) { + setStockProductsLocationId(selectedLocation.value.toString()); + setStockProductsKandangId(kandang.value.toString()); + setDepletionProductsLocationId(selectedLocation.value.toString()); + setDepletionProductsKandangId(kandang.value.toString()); + setEggProductsLocationId(selectedLocation.value.toString()); + setEggProductsKandangId(kandang.value.toString()); + } else { + setStockProductsLocationId(''); + setStockProductsKandangId(''); + setDepletionProductsLocationId(''); + setDepletionProductsKandangId(''); + setEggProductsLocationId(''); + setEggProductsKandangId(''); + } formik.setFieldTouched('project_flock_kandang', true); formik.setFieldTouched('project_flock_kandang_id', true); }; @@ -1091,6 +1040,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { label: location.name || '', }; setSelectedLocation(locationOption); + setSelectedProjectFlockLocationId(location.id.toString()); if (projectFlock) { const projectFlockOption = { @@ -1106,6 +1056,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; setSelectedKandang(kandangOption); + setStockProductsLocationId(location.id.toString()); + setStockProductsKandangId(kandang.id.toString()); + setDepletionProductsLocationId(location.id.toString()); + setDepletionProductsKandangId(kandang.id.toString()); + setEggProductsLocationId(location.id.toString()); + setEggProductsKandangId(kandang.id.toString()); + if ( formik.values.project_flock_kandang_id !== projectFlockKandangDetail.id @@ -1126,7 +1083,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [ projectFlockKandangDetail, type, - projectFlockOptions, + enhancedProjectFlockOptions, formik.values.project_flock_kandang_id, ]); @@ -1415,23 +1372,25 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { label='Lokasi' value={selectedLocation} onChange={locationChangeHandler} - options={locationOptions} + options={enhancedLocationOptions} onInputChange={setLocationSearchValue} isLoading={isLoadingLocations} + onMenuScrollToBottom={loadMoreLocations} placeholder='Pilih Lokasi' isClearable isSearchable /> Date: Thu, 15 Jan 2026 10:51:33 +0700 Subject: [PATCH 22/47] refactor(FE): Add onMenuScrollToBottom to product selects --- .../pages/production/recording/form/RecordingForm.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index f492c987..db61080e 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1936,6 +1936,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options={unifiedStockProducts} placeholder='Pilih Produk' isLoading={isLoadingStockProducts} + onMenuScrollToBottom={loadMoreStockProducts} isError={ isRepeaterInputError( 'stocks', @@ -2157,6 +2158,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options={depletionProducts} placeholder='Pilih Kondisi' isLoading={isLoadingDepletionProducts} + onMenuScrollToBottom={loadMoreDepletionProducts} isError={ isRepeaterInputError( 'depletions', @@ -2376,6 +2378,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options={eggProducts} placeholder='Pilih Kondisi Telur' isLoading={isLoadingEggProducts} + onMenuScrollToBottom={loadMoreEggProducts} isError={ isRepeaterInputError( 'eggs', From 294c843bd480e3414aa65afe3d3aad3d52935489 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 11:58:38 +0700 Subject: [PATCH 23/47] refactor(FE): Use useSelect for project flock filter --- .../production/uniformity/UniformityTable.tsx | 77 ++++++++----------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index 63c446ac..c2049ab1 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -37,7 +37,10 @@ import DateInput from '@/components/input/DateInput'; import { LocationApi } from '@/services/api/master-data'; import { ProjectFlockApi } from '@/services/api/production'; import { Kandang } from '@/types/api/master-data/kandang'; -import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; +import { + ProjectFlockKandangLookup, + ProjectFlock, +} from '@/types/api/production/project-flock'; import { getStatusColor, getStatusIndicatorColor, @@ -229,63 +232,37 @@ const UniformityTable = () => { useState(undefined); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); - const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); + const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] = + useState(''); const [filterErrors, setFilterErrors] = useState>({}); const { setInputValue: setFilterLocationInputValue, options: filterLocationOptions, isLoadingOptions: isLoadingFilterLocations, - } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { - limit: '100', - }); + loadMore: loadMoreFilterLocations, + hasMore: hasMoreFilterLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); // ===== FETCH PROJECT FLOCKS DATA FOR FILTER ===== - const filterProjectFlocksUrl = useMemo(() => { - const params = new URLSearchParams({ - search: projectFlockSearchValue || '', - limit: '100', - }); - if (filterLocation) { - params.append('location_id', filterLocation.value.toString()); - } - return `${ProjectFlockApi.basePath}?${params.toString()}`; - }, [projectFlockSearchValue, filterLocation]); - const { - data: filterProjectFlocksData, - isLoading: isLoadingFilterProjectFlocks, - } = useSWR(filterProjectFlocksUrl, ProjectFlockApi.getAllFetcher); - - const filterProjectFlocksDataList = useMemo( - () => - isResponseSuccess(filterProjectFlocksData) - ? filterProjectFlocksData.data - : undefined, - [filterProjectFlocksData] - ); - - const filterProjectFlockOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(filterProjectFlocksData)) { - const flockOptions = - filterProjectFlocksData?.data.map((projectFlock) => ({ - value: projectFlock.id, - label: projectFlock.flock_name || '', - })) || []; - options = options.concat(flockOptions); - } - - return options; - }, [filterProjectFlocksData]); + setInputValue: setFilterProjectFlockSearchValue, + options: filterProjectFlockOptions, + rawData: filterProjectFlocksRawData, + isLoadingOptions: isLoadingFilterProjectFlocks, + loadMore: loadMoreFilterProjectFlocks, + hasMore: hasMoreFilterProjectFlocks, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { + location_id: filterProjectFlockLocationId, + }); // ===== KANDANG OPTIONS FOR FILTER ===== const filterKandangOptions = useMemo(() => { let options: OptionType[] = []; - if (filterProjectFlock && filterProjectFlocksDataList) { - const selectedProjectFlockData = filterProjectFlocksDataList.find( + if (filterProjectFlock && isResponseSuccess(filterProjectFlocksRawData)) { + const data = filterProjectFlocksRawData.data as unknown as ProjectFlock[]; + const selectedProjectFlockData = data.find( (pf) => pf.id === filterProjectFlock.value ); @@ -301,7 +278,7 @@ const UniformityTable = () => { } return options; - }, [filterProjectFlock, filterProjectFlocksDataList]); + }, [filterProjectFlock, filterProjectFlocksRawData]); // ===== PROJECT FLOCK KANDANG LOOKUP ===== const projectFlockKandangLookupUrl = useMemo(() => { @@ -394,9 +371,13 @@ const UniformityTable = () => { // ===== FILTER HANDLERS ===== const handleFilterLocationChange = useCallback( (val: OptionType | OptionType[] | null) => { - setFilterLocation(val as OptionType | null); + const location = val as OptionType | null; + setFilterLocation(location); setFilterProjectFlock(null); setFilterKandang(null); + setFilterProjectFlockLocationId( + location ? location.value.toString() : '' + ); }, [] ); @@ -1206,6 +1187,7 @@ const UniformityTable = () => { options={filterLocationOptions} onInputChange={setFilterLocationInputValue} isLoading={isLoadingFilterLocations} + onMenuScrollToBottom={loadMoreFilterLocations} className={{ wrapper: 'w-full' }} /> {filterErrors.location && ( @@ -1225,8 +1207,9 @@ const UniformityTable = () => { setFilterErrors((prev) => ({ ...prev, project_flock: '' })); }} options={filterProjectFlockOptions} - onInputChange={setProjectFlockSearchValue} + onInputChange={setFilterProjectFlockSearchValue} isLoading={isLoadingFilterProjectFlocks} + onMenuScrollToBottom={loadMoreFilterProjectFlocks} isDisabled={!filterLocation} className={{ wrapper: 'w-full' }} /> From dc3b4f1850174873ea1518bef00b770bb9a0bd07 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 12:05:34 +0700 Subject: [PATCH 24/47] refactor(FE): Use useSelect for ProjectFlock with pagination --- .../uniformity/form/UniformityForm.tsx | 77 ++++++++----------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index 54b4ee2b..f46a15b3 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -36,7 +36,10 @@ import { VerifyUniformityPayload, } from '@/types/api/production/uniformity'; import { type BaseApiResponse } from '@/types/api/api-general'; -import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; +import { + ProjectFlockKandangLookup, + ProjectFlock, +} from '@/types/api/production/project-flock'; 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'; @@ -88,7 +91,9 @@ const UniformityForm = ({ null ); - const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); + const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] = + useState(''); + const [selectedProjectFlock, setSelectedProjectFlock] = useState(null); @@ -100,50 +105,21 @@ const UniformityForm = ({ setInputValue: setLocationSelectInputValue, options: locationOptions, isLoadingOptions: isLoadingLocations, - } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { - page: '1', - limit: '100', + loadMore: loadMoreLocations, + hasMore: hasMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); + + const { + setInputValue: setProjectFlockSearchValue, + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlocks, + loadMore: loadMoreProjectFlocks, + hasMore: hasMoreProjectFlocks, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { + location_id: selectedProjectFlockLocationId, }); - // ===== FETCH PROJECT FLOCKS DATA ===== - const projectFlocksUrl = useMemo(() => { - const params = new URLSearchParams({ - search: projectFlockSearchValue || '', - page: '1', - limit: '100', - }); - if (selectedLocation) { - params.append('location_id', selectedLocation.value.toString()); - } - return `${ProjectFlockApi.basePath}?${params.toString()}`; - }, [projectFlockSearchValue, selectedLocation]); - - const { data: projectFlocksData, isLoading: isLoadingProjectFlocks } = useSWR( - projectFlocksUrl, - ProjectFlockApi.getAllFetcher - ); - - const projectFlocksDataList = - projectFlocksData?.status === 'success' - ? projectFlocksData.data - : undefined; - - // ===== PROJECT FLOCK OPTIONS ===== - const projectFlockOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(projectFlocksData)) { - const flockOptions = - projectFlocksData?.data.map((projectFlock) => ({ - value: projectFlock.id, - label: projectFlock.flock_name || '', - })) || []; - options = options.concat(flockOptions); - } - - return options; - }, [projectFlocksData]); - // ===== APPROVED PROJECT FLOCK KANDANGS ===== const approvedProjectFlockKandangsUrl = useMemo(() => { const params = new URLSearchParams({ @@ -168,8 +144,9 @@ const UniformityForm = ({ const kandangOptions = useMemo(() => { let options: OptionType[] = []; - if (selectedProjectFlock && projectFlocksDataList) { - const selectedProjectFlockData = projectFlocksDataList.find( + if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) { + const data = projectFlocksRawData.data as unknown as ProjectFlock[]; + const selectedProjectFlockData = data.find( (pf) => pf.id === selectedProjectFlock.value ); @@ -196,7 +173,7 @@ const UniformityForm = ({ return options; }, [ selectedProjectFlock, - projectFlocksDataList, + projectFlocksRawData, approvedProjectFlockKandangs, formType, ]); @@ -313,6 +290,10 @@ const UniformityForm = ({ formik.setFieldValue('location_id', locationId); setSelectedLocation(location); + setSelectedProjectFlock(null); + setSelectedProjectFlockLocationId( + location ? location.value.toString() : '' + ); }, [] ); @@ -513,6 +494,7 @@ const UniformityForm = ({ options={locationOptions} onInputChange={setLocationSelectInputValue} isLoading={isLoadingLocations} + onMenuScrollToBottom={loadMoreLocations} isError={ formik.touched.location_id && Boolean(formik.errors.location_id) } @@ -530,6 +512,7 @@ const UniformityForm = ({ options={projectFlockOptions} onInputChange={setProjectFlockSearchValue} isLoading={isLoadingProjectFlocks} + onMenuScrollToBottom={loadMoreProjectFlocks} isDisabled={!formik.values.location_id} isError={ formik.touched.project_flock_id && From c9bace04ec0b1342132a755ab305b762f6617655 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 12:07:55 +0700 Subject: [PATCH 25/47] refactor(FE): Use absolute import for Badge --- .../pages/production/uniformity/chart/UniformityStat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/production/uniformity/chart/UniformityStat.tsx b/src/components/pages/production/uniformity/chart/UniformityStat.tsx index ea8a5c0e..e7603e16 100644 --- a/src/components/pages/production/uniformity/chart/UniformityStat.tsx +++ b/src/components/pages/production/uniformity/chart/UniformityStat.tsx @@ -1,4 +1,4 @@ -import Badge from '../../../../Badge'; +import Badge from '@/components/Badge'; import Card from '@/components/Card'; import { Icon } from '@iconify/react'; import { formatNumber } from '@/lib/helper'; From f3b109189096f80e72ca0391b9bb6b4ca8cd1a00 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 12:12:03 +0700 Subject: [PATCH 26/47] refactor(FE): Add infinite scroll to flock selects --- .../transfer-to-laying/TransferToLayingsTable.tsx | 6 ++++++ .../transfer-to-laying/form/TransferToLayingForm.tsx | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx index 18ce404d..860c4616 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx @@ -179,12 +179,16 @@ const TransferToLayingsTable = () => { setInputValue: setFlockSourceInputValue, options: flockSourceOptions, isLoadingOptions: isLoadingFlockSourceOptions, + loadMore: loadMoreFlockSource, + hasMore: hasMoreFlockSource, } = useSelect(FlockApi.basePath, 'id', 'name'); const { setInputValue: setFlockDestinationInputValue, options: flockDestinationOptions, isLoadingOptions: isLoadingFlockDestinationOptions, + loadMore: loadMoreFlockDestination, + hasMore: hasMoreFlockDestination, } = useSelect(FlockApi.basePath, 'id', 'name'); // Flocks value @@ -595,6 +599,7 @@ const TransferToLayingsTable = () => { value={selectedFlockSource} onChange={flockSourceChangeHandler} onInputChange={setFlockSourceInputValue} + onMenuScrollToBottom={loadMoreFlockSource} isClearable className={{ wrapper: 'col-span-12 sm:col-span-3', @@ -608,6 +613,7 @@ const TransferToLayingsTable = () => { value={selectedFlockDestination} onChange={flockDestinationChangeHandler} onInputChange={setFlockDestinationInputValue} + onMenuScrollToBottom={loadMoreFlockDestination} isClearable className={{ wrapper: 'col-span-12 sm:col-span-3', diff --git a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx index c5683fff..a257af0d 100644 --- a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx +++ b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx @@ -270,6 +270,8 @@ const TransferToLayingForm = ({ options: flockSourceOptions, isLoadingOptions: isLoadingFlockSourceOptions, rawData: flockSources, + loadMore: loadMoreFlockSource, + hasMore: hasMoreFlockSource, } = useSelect( '/production/project-flocks', 'id', @@ -360,6 +362,8 @@ const TransferToLayingForm = ({ options: flockDestinationOptions, isLoadingOptions: isLoadingFlockDestinationOptions, rawData: flockDestinations, + loadMore: loadMoreFlockDestination, + hasMore: hasMoreFlockDestination, } = useSelect( '/production/project-flocks', 'id', @@ -573,6 +577,7 @@ const TransferToLayingForm = ({ onChange={flockSourceChangeHandler} isLoading={isLoadingFlockSourceOptions} onInputChange={setFlockSourceInputValue} + onMenuScrollToBottom={loadMoreFlockSource} isError={ formik.touched.flockSource && Boolean(typeof formik.errors.flockSource === 'string') @@ -591,6 +596,7 @@ const TransferToLayingForm = ({ onChange={flockDestinationChangeHandler} isLoading={isLoadingFlockDestinationOptions} onInputChange={setFlockDestinationInputValue} + onMenuScrollToBottom={loadMoreFlockDestination} isError={ formik.touched.flockDestination && Boolean(typeof formik.errors.flockDestination === 'string') From 817420ee625a084d4ae0676e80088e560a941481 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 13:35:00 +0700 Subject: [PATCH 27/47] refactor(FE): Rename Ayam labels to Telur in HPP reports --- .../sale/export/HppPerkandangExport.tsx | 10 +++--- .../report/sale/tab/HppPerKandangTab.tsx | 32 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx index 0a712a6c..6883bdfb 100644 --- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -226,7 +226,7 @@ const createPDFDocument = ( Rentang BW - Sisa Ekor + Sisa Butir Sisa Kg @@ -253,7 +253,7 @@ const createPDFDocument = ( Nilai Nominal Telur - HPP Ayam + HPP Telur HPP Telur (RP/KG) @@ -356,10 +356,10 @@ const createPDFDocument = ( Rata-Rata Bobot (Kg) - Sisa Ekor + Sisa Butir - Sisa Kg (Ayam) + Sisa Kg (Telur) Produksi Telur (Butir) @@ -380,7 +380,7 @@ const createPDFDocument = ( Nilai Nominal Telur - HPP Ayam + HPP Telur HPP Telur (RP/KG) diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index eda88d8c..8e669878 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -335,8 +335,8 @@ const HppPerKandangTab = () => { ? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}` : '', 'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0, - 'Sisa Ayam (Ekor)': item.remaining_chicken_birds || 0, - 'Sisa Ayam (KG)': item.remaining_chicken_weight_kg || 0, + 'Sisa Telur (Butir)': item.remaining_chicken_birds || 0, + 'Sisa Telur (KG)': item.remaining_chicken_weight_kg || 0, 'Produksi Telur (Butir)': item.egg_production_pieces || 0, 'Produksi Telur (KG)': item.egg_production_kg || 0, 'Feed (Supplier)': @@ -349,9 +349,9 @@ const HppPerKandangTab = () => { .join(' | ') || '', 'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0, 'Nilai Nominal Telur (RP)': item.egg_value_rp || 0, - 'HPP Ayam (RP)': item.hpp_rp || 0, + 'HPP Telur (RP)': item.hpp_rp || 0, 'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0, - 'Nilai Nominal Sisa Ayam (RP)': item.remaining_value_rp || 0, + 'Nilai Nominal Sisa Telur (RP)': item.remaining_value_rp || 0, }) ); @@ -360,8 +360,8 @@ const HppPerKandangTab = () => { Kandang: 'ALL', 'Rentang Bobot': '-', 'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0, - 'Sisa Ayam (Ekor)': summaryTotal?.total_remaining_chicken_birds || 0, - 'Sisa Ayam (KG)': summaryTotal?.total_remaining_chicken_weight_kg || 0, + 'Sisa Telur (Butir)': summaryTotal?.total_remaining_chicken_birds || 0, + 'Sisa Telur (KG)': summaryTotal?.total_remaining_chicken_weight_kg || 0, 'Produksi Telur (Butir)': summaryTotal?.total_egg_production_pieces || 0, 'Produksi Telur (KG)': summaryTotal?.total_egg_production_kg || 0, @@ -370,9 +370,9 @@ const HppPerKandangTab = () => { 'Rata-Rata Harga DOC (RP)': summaryTotal?.total_average_doc_price_rp || 0, 'Nilai Nominal Telur (RP)': summaryTotal?.total_egg_value_rp || 0, - 'HPP Ayam (RP)': summaryTotal?.total_hpp_rp || 0, + 'HPP Telur (RP)': summaryTotal?.total_hpp_rp || 0, 'HPP Telur (RP/KG)': summaryTotal?.average_egg_hpp_rp_per_kg || 0, - 'Nilai Nominal Sisa Ayam (RP)': + 'Nilai Nominal Sisa Telur (RP)': summaryTotal?.total_remaining_value_rp || 0, }); @@ -383,17 +383,17 @@ const HppPerKandangTab = () => { { wch: 30 }, // Kandang { wch: 15 }, // Rentang Bobot { wch: 18 }, // Rata-Rata Bobot (KG) - { wch: 15 }, // Sisa Ayam (Ekor) - { wch: 15 }, // Sisa Ayam (KG) + { wch: 15 }, // Sisa Telur (Butir) + { wch: 15 }, // Sisa Telur (KG) { wch: 18 }, // Produksi Telur (Butir) { wch: 18 }, // Produksi Telur (KG) { wch: 20 }, // Feed (Supplier) { wch: 20 }, // DOC (Supplier) { wch: 20 }, // Rata-Rata Harga DOC (RP) { wch: 20 }, // Nilai Nominal Telur (RP) - { wch: 15 }, // HPP Ayam (RP) + { wch: 15 }, // HPP Telur (RP) { wch: 18 }, // HPP Telur (RP/KG) - { wch: 25 }, // Nilai Nominal Sisa Ayam (RP) + { wch: 25 }, // Nilai Nominal Sisa Telur (RP) ]; worksheet['!cols'] = colWidths; @@ -535,7 +535,7 @@ const HppPerKandangTab = () => { }, { id: 'remaining_chicken_birds', - header: 'Sisa Ayam (Ekor)', + header: 'Sisa Telur (Butir)', accessorKey: 'remaining_chicken_birds', cell: (props) => { const value = props.row.original.remaining_chicken_birds; @@ -549,7 +549,7 @@ const HppPerKandangTab = () => { }, { id: 'remaining_chicken_weight_kg', - header: 'Sisa Ayam (KG)', + header: 'Sisa Telur (KG)', accessorKey: 'remaining_chicken_weight_kg', cell: (props) => { const value = props.row.original.remaining_chicken_weight_kg; @@ -655,7 +655,7 @@ const HppPerKandangTab = () => { }, { id: 'hpp_rp', - header: 'HPP Ayam (RP)', + header: 'HPP Telur (RP)', accessorKey: 'hpp_rp', cell: (props) => { const value = props.row.original.hpp_rp; @@ -683,7 +683,7 @@ const HppPerKandangTab = () => { }, { id: 'remaining_value_rp', - header: 'Nilai Nominal Sisa Ayam (RP)', + header: 'Nilai Nominal Sisa Telur (RP)', accessorKey: 'remaining_value_rp', cell: (props) => { const value = props.row.original.remaining_value_rp; From 9d7140beb611885b0a9b0ae81e604518a05f4351 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 13:52:21 +0700 Subject: [PATCH 28/47] refactor(FE): Use egg production as remaining columns --- .../sale/export/HppPerkandangExport.tsx | 60 +--------- .../report/sale/tab/HppPerKandangTab.tsx | 110 +++--------------- src/types/api/report/hpp-per-kandang.d.ts | 12 -- 3 files changed, 20 insertions(+), 162 deletions(-) diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx index 6883bdfb..95f42df6 100644 --- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -234,12 +234,6 @@ const createPDFDocument = ( Rata-Rata Bobot (Kg) - - Produksi Telur (Butir) - - - Produksi Telur (Kg) - Feed (Supplier) @@ -249,12 +243,6 @@ const createPDFDocument = ( Rata-Rata Harga DOC - - Nilai Nominal Telur - - - HPP Telur - HPP Telur (RP/KG) @@ -278,23 +266,15 @@ const createPDFDocument = ( {group.label} - - {formatNumber(group.remaining_chicken_birds)} - - - - {formatNumber(group.remaining_chicken_weight_kg)} - - - - {formatNumber(group.avg_weight_kg)} - {formatNumber(group.egg_production_pieces)} {formatNumber(group.egg_production_kg)} + + {formatNumber(group.avg_weight_kg)} + {group.feed_suppliers @@ -318,17 +298,11 @@ const createPDFDocument = ( {formatCurrency(group.average_doc_price_rp)} - - {formatCurrency(group.egg_value_rp)} - - - {formatCurrency(group.hpp_rp)} - {formatCurrency(group.egg_hpp_rp_per_kg)} - {formatCurrency(group.remaining_value_rp)} + {formatCurrency(group.egg_value_rp)} ) @@ -361,12 +335,6 @@ const createPDFDocument = ( Sisa Kg (Telur) - - Produksi Telur (Butir) - - - Produksi Telur (Kg) - Feed (Supplier) @@ -376,12 +344,6 @@ const createPDFDocument = ( Rata-Rata Harga DOC - - Nilai Nominal Telur - - - HPP Telur - HPP Telur (RP/KG) @@ -416,12 +378,6 @@ const createPDFDocument = ( {formatNumber(item.avg_weight_kg)} - - {formatNumber(item.remaining_chicken_birds)} - - - {formatNumber(item.remaining_chicken_weight_kg)} - {formatNumber(item.egg_production_pieces)} @@ -451,17 +407,11 @@ const createPDFDocument = ( {formatCurrency(item.average_doc_price_rp)} - - {formatCurrency(item.egg_value_rp)} - - - {formatCurrency(item.hpp_rp)} - {formatCurrency(item.egg_hpp_rp_per_kg)} - {formatCurrency(item.remaining_value_rp)} + {formatCurrency(item.egg_value_rp)} ))} diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index 8e669878..22d80220 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -335,10 +335,8 @@ const HppPerKandangTab = () => { ? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}` : '', 'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0, - 'Sisa Telur (Butir)': item.remaining_chicken_birds || 0, - 'Sisa Telur (KG)': item.remaining_chicken_weight_kg || 0, - 'Produksi Telur (Butir)': item.egg_production_pieces || 0, - 'Produksi Telur (KG)': item.egg_production_kg || 0, + 'Sisa Telur (Butir)': item.egg_production_pieces || 0, + 'Sisa Telur (KG)': item.egg_production_kg || 0, 'Feed (Supplier)': item.feed_suppliers ?.map((s: { alias?: string; name: string }) => s.alias || s.name) @@ -348,10 +346,8 @@ const HppPerKandangTab = () => { ?.map((s: { alias?: string; name: string }) => s.alias || s.name) .join(' | ') || '', 'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0, - 'Nilai Nominal Telur (RP)': item.egg_value_rp || 0, - 'HPP Telur (RP)': item.hpp_rp || 0, 'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0, - 'Nilai Nominal Sisa Telur (RP)': item.remaining_value_rp || 0, + 'Nilai Nominal Sisa Telur (RP)': item.egg_value_rp || 0, }) ); @@ -360,20 +356,14 @@ const HppPerKandangTab = () => { Kandang: 'ALL', 'Rentang Bobot': '-', 'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0, - 'Sisa Telur (Butir)': summaryTotal?.total_remaining_chicken_birds || 0, - 'Sisa Telur (KG)': summaryTotal?.total_remaining_chicken_weight_kg || 0, - 'Produksi Telur (Butir)': - summaryTotal?.total_egg_production_pieces || 0, - 'Produksi Telur (KG)': summaryTotal?.total_egg_production_kg || 0, + 'Sisa Telur (Butir)': summaryTotal?.total_egg_production_pieces || 0, + 'Sisa Telur (KG)': summaryTotal?.total_egg_production_kg || 0, 'Feed (Supplier)': allFeedSuppliers, 'DOC (Supplier)': allDocSuppliers, 'Rata-Rata Harga DOC (RP)': summaryTotal?.total_average_doc_price_rp || 0, - 'Nilai Nominal Telur (RP)': summaryTotal?.total_egg_value_rp || 0, - 'HPP Telur (RP)': summaryTotal?.total_hpp_rp || 0, 'HPP Telur (RP/KG)': summaryTotal?.average_egg_hpp_rp_per_kg || 0, - 'Nilai Nominal Sisa Telur (RP)': - summaryTotal?.total_remaining_value_rp || 0, + 'Nilai Nominal Sisa Telur (RP)': summaryTotal?.total_egg_value_rp || 0, }); const worksheet = XLSX.utils.json_to_sheet(excelData); @@ -385,13 +375,9 @@ const HppPerKandangTab = () => { { wch: 18 }, // Rata-Rata Bobot (KG) { wch: 15 }, // Sisa Telur (Butir) { wch: 15 }, // Sisa Telur (KG) - { wch: 18 }, // Produksi Telur (Butir) - { wch: 18 }, // Produksi Telur (KG) { wch: 20 }, // Feed (Supplier) { wch: 20 }, // DOC (Supplier) { wch: 20 }, // Rata-Rata Harga DOC (RP) - { wch: 20 }, // Nilai Nominal Telur (RP) - { wch: 15 }, // HPP Telur (RP) { wch: 18 }, // HPP Telur (RP/KG) { wch: 25 }, // Nilai Nominal Sisa Telur (RP) ]; @@ -533,37 +519,9 @@ const HppPerKandangTab = () => {
), }, - { - id: 'remaining_chicken_birds', - header: 'Sisa Telur (Butir)', - accessorKey: 'remaining_chicken_birds', - cell: (props) => { - const value = props.row.original.remaining_chicken_birds; - return
{formatNumber(value)}
; - }, - footer: () => ( -
- {formatNumber(summaryTotal?.total_remaining_chicken_birds || 0)} -
- ), - }, - { - id: 'remaining_chicken_weight_kg', - header: 'Sisa Telur (KG)', - accessorKey: 'remaining_chicken_weight_kg', - cell: (props) => { - const value = props.row.original.remaining_chicken_weight_kg; - return
{formatNumber(value)}
; - }, - footer: () => ( -
- {formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)} -
- ), - }, { id: 'egg_production_pieces', - header: 'Produksi Telur (Butir)', + header: 'Sisa Telur (Butir)', accessorKey: 'egg_production_pieces', cell: (props) => { const value = props.row.original.egg_production_pieces; @@ -577,7 +535,7 @@ const HppPerKandangTab = () => { }, { id: 'egg_production_kg', - header: 'Produksi Telur (KG)', + header: 'Sisa Telur (KG)', accessorKey: 'egg_production_kg', cell: (props) => { const value = props.row.original.egg_production_kg; @@ -585,7 +543,7 @@ const HppPerKandangTab = () => { }, footer: () => (
- {formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)} + {formatNumber(summaryTotal?.total_egg_production_kg || 0)}
), }, @@ -639,34 +597,6 @@ const HppPerKandangTab = () => {
), }, - { - id: 'egg_value_rp', - header: 'Nilai Nominal Telur (RP)', - accessorKey: 'egg_value_rp', - cell: (props) => { - const value = props.row.original.egg_value_rp; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(summaryTotal?.total_egg_value_rp || 0)} -
- ), - }, - { - id: 'hpp_rp', - header: 'HPP Telur (RP)', - accessorKey: 'hpp_rp', - cell: (props) => { - const value = props.row.original.hpp_rp; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(summaryTotal?.total_hpp_rp || 0)} -
- ), - }, { id: 'egg_hpp_rp_per_kg', header: 'HPP Telur (RP/KG)', @@ -682,16 +612,16 @@ const HppPerKandangTab = () => { ), }, { - id: 'remaining_value_rp', + id: 'egg_value_rp', header: 'Nilai Nominal Sisa Telur (RP)', - accessorKey: 'remaining_value_rp', + accessorKey: 'egg_value_rp', cell: (props) => { - const value = props.row.original.remaining_value_rp; + const value = props.row.original.egg_value_rp; return
{formatCurrency(value)}
; }, footer: () => (
- {formatCurrency(summaryTotal?.total_remaining_value_rp || 0)} + {formatCurrency(summaryTotal?.total_egg_value_rp || 0)}
), }, @@ -725,7 +655,7 @@ const HppPerKandangTab = () => { key={'rekapitulasi-row'} >
- - @@ -772,15 +696,11 @@ const HppPerKandangTab = () => { - - ); diff --git a/src/types/api/report/hpp-per-kandang.d.ts b/src/types/api/report/hpp-per-kandang.d.ts index 824a3837..1b3f7fcd 100644 --- a/src/types/api/report/hpp-per-kandang.d.ts +++ b/src/types/api/report/hpp-per-kandang.d.ts @@ -9,8 +9,6 @@ export type HppPerKandangRow = { weight_min: number; weight_max: number; }; - remaining_chicken_birds: number; - remaining_chicken_weight_kg: number; avg_weight_kg: number; egg_production_pieces: number; egg_production_kg: number; @@ -19,20 +17,14 @@ export type HppPerKandangRow = { feed_suppliers: Supplier[]; doc_suppliers: Supplier[]; average_doc_price_rp: number; - hpp_rp: number; - remaining_value_rp: number; }; export type HppPerKandangSummaryTotal = { - total_remaining_chicken_birds: number; - total_remaining_chicken_weight_kg: number; average_weight_kg: number; - total_remaining_value_rp: number; total_egg_production_pieces: number; total_egg_production_kg: number; average_egg_hpp_rp_per_kg: number; total_egg_value_rp: number; - total_hpp_rp: number; total_average_doc_price_rp: number; }; @@ -43,8 +35,6 @@ export type HppPerKandangPerWeightRange = { weight_max: number; }; label: string; - remaining_chicken_birds: number; - remaining_chicken_weight_kg: number; avg_weight_kg: number; egg_production_pieces: number; egg_production_kg: number; @@ -53,8 +43,6 @@ export type HppPerKandangPerWeightRange = { feed_suppliers: Supplier[]; doc_suppliers: Supplier[]; average_doc_price_rp: number; - hpp_rp: number; - remaining_value_rp: number; }; export type HppPerKandangSummary = { From dd080b1d19c355f7c3168eec00d92cf3fd2f07da Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 14:08:09 +0700 Subject: [PATCH 29/47] refactor(FE): Add record_date and DateInput to recording form --- .../recording/form/RecordingForm.schema.ts | 7 +++++ .../recording/form/RecordingForm.tsx | 29 +++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 4901b349..8ebd4aa2 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -7,6 +7,7 @@ import { } from '@/types/api/production/recording'; type RecordingGrowingFormSchemaType = { + record_date: string; project_flock_kandang: { value: number; label: string; @@ -85,6 +86,9 @@ const EggObjectSchema: Yup.ObjectSchema = Yup.object({ export const RecordingGrowingFormSchema: Yup.ObjectSchema = Yup.object({ + record_date: Yup.string() + .required('Tanggal recording wajib diisi!') + .typeError('Tanggal recording wajib diisi!'), project_flock_kandang: Yup.object({ value: Yup.number().min(1).required(), label: Yup.string().required(), @@ -179,6 +183,9 @@ type RecordingFormData = Partial & { export const getRecordingGrowingFormInitialValues = ( initialValues?: RecordingFormData ): RecordingGrowingFormValues => ({ + record_date: initialValues?.record_datetime + ? new Date(initialValues.record_datetime).toISOString().split('T')[0] + : new Date().toISOString().split('T')[0], project_flock_kandang: initialValues?.project_flock_kandang_id ? { value: initialValues.project_flock_kandang_id, diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index db61080e..c418c5b2 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -12,6 +12,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import Card from '@/components/Card'; import Badge from '@/components/Badge'; import NumberInput from '@/components/input/NumberInput'; +import DateInput from '@/components/input/DateInput'; import SelectInput, { OptionType, useSelect, @@ -135,10 +136,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== PAYLOAD CREATION HELPERS ===== const createGrowingPayload = useCallback( (values: RecordingGrowingFormValues) => { - const today = new Date().toISOString().split('T')[0]; return { project_flock_kandang_id: values.project_flock_kandang_id, - record_date: today, + record_date: values.record_date, stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, @@ -154,10 +154,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const createLayingPayload = useCallback( (values: RecordingLayingFormValues) => { - const today = new Date().toISOString().split('T')[0]; return { project_flock_kandang_id: values.project_flock_kandang_id, - record_date: today, + record_date: values.record_date, stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, @@ -971,6 +970,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { formik.setFieldTouched('project_flock_kandang_id', true); }; + const handleRecordDateChange = useCallback( + (e: React.ChangeEvent) => { + formik.setFieldValue('record_date', e.target.value); + }, + [formik] + ); + useEffect(() => { if (projectFlockKandangLookup?.project_flock_kandang_id) { const projectFlockKandangId = @@ -1364,8 +1370,21 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { body: 'flex flex-col gap-6', }} > -
+
<> + Date: Thu, 15 Jan 2026 14:47:49 +0700 Subject: [PATCH 30/47] feat(FE): Add Periode and Gudang columns and show week --- .../production/recording/RecordingTable.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 96ac52ed..5d445fe1 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -690,6 +690,10 @@ const RecordingTable = () => { cell: (props) => props.row.original.project_flock?.flock_name || '-', }, + { + header: 'Periode', + cell: (props) => props.row.original.project_flock?.period || '-', + }, { header: 'Kategori', cell: (props) => { @@ -706,7 +710,21 @@ const RecordingTable = () => { }, { header: 'Umur (hari)', - cell: (props) => props.row.original.day, + cell: (props) => { + return ( + <> + + {props.row.original.day} (Minggu ke- + {props.row.original.project_flock.production_standart.week}) + + + ); + }, + }, + { + accessorKey: 'warehouse.name', + header: 'Gudang', + cell: (props) => props.row.original.warehouse?.name, }, { accessorKey: 'record_date', @@ -715,7 +733,7 @@ const RecordingTable = () => { formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), }, { - header: 'Populasi Ayam', + header: 'Populasi Akhir', cell: (props) => props.row.original.project_flock?.total_chick_qty?.toLocaleString() || '-', From ac84841b05516f9d16cabac9dc5ae70d468bd0e5 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 15:06:41 +0700 Subject: [PATCH 31/47] feat(FE): Add production metric columns and table styling --- .../production/recording/RecordingTable.tsx | 272 +++++++++++++++++- 1 file changed, 263 insertions(+), 9 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 5d445fe1..b854cd59 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -5,7 +5,7 @@ import { RefObject } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import { SortingState, CellContext } from '@tanstack/react-table'; -import { cn, formatDate } from '@/lib/helper'; +import { cn, formatDate, formatNumber } from '@/lib/helper'; import RequirePermission from '@/components/helper/RequirePermission'; import { useModal } from '@/components/Modal'; import Modal from '@/components/Modal'; @@ -679,7 +679,7 @@ const RecordingTable = () => { }, }, { - header: '#', + header: 'No', cell: (props) => tableFilterState.pageSize * (tableFilterState.page - 1) + props.row.index + @@ -735,8 +735,261 @@ const RecordingTable = () => { { header: 'Populasi Akhir', cell: (props) => - props.row.original.project_flock?.total_chick_qty?.toLocaleString() || - '-', + props.row.original.project_flock?.total_chick_qty != null + ? formatNumber(props.row.original.project_flock.total_chick_qty) + : '-', + }, + { + id: 'fcr', + header: 'FCR', + columns: [ + { + id: 'fcr_actual', + header: 'Actual', + cell: (props) => { + const value = props.row.original.fcr_value; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'fcr_standard', + header: 'Standard', + cell: (props) => { + const value = props.row.original.project_flock?.fcr?.fcr_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'feed_intake', + header: 'Feed Intake (KG)', + columns: [ + { + id: 'feed_intake_actual', + header: 'Actual', + cell: (props) => { + const value = props.row.original.feed_intake; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'feed_intake_standard', + header: 'Standard', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.feed_intake_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'mortality', + header: 'Mortality', + columns: [ + { + id: 'cum_depletion_rate_actual', + header: 'Cum Depletion Rate', + cell: (props) => { + const value = props.row.original.cum_depletion_rate; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'max_depletion_std', + header: 'Max Depletion Std', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.max_depletion_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'total_depletion', + header: 'Total Depletion', + cell: (props) => { + const value = props.row.original.total_depletion_qty; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'egg_production', + header: 'Egg Production', + columns: [ + { + id: 'egg_mass_actual', + header: 'Egg Mass Actual', + cell: (props) => { + const value = props.row.original.egg_mass; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_mass_standard', + header: 'Egg Mass Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.egg_mass_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_weight_actual', + header: 'Egg Weight Actual', + cell: (props) => { + const value = props.row.original.egg_weight; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_weight_standard', + header: 'Egg Weight Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.egg_weight_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'hen_performance', + header: 'Hen Performance', + columns: [ + { + id: 'hen_day_actual', + header: 'Hen Day Actual', + cell: (props) => { + const value = props.row.original.hen_day; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_day_standard', + header: 'Hen Day Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.hen_day_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_house_actual', + header: 'Hen House Actual', + cell: (props) => { + const value = props.row.original.hen_house; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_house_standard', + header: 'Hen House Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.hen_house_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + ], }, { header: 'Status Approval', @@ -902,14 +1155,15 @@ const RecordingTable = () => { 'mb-20': isResponseSuccess(recordings) && recordings?.data?.length === 0, }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: 'font-inter w-full table-auto min-h-full!', + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', headerRowClassName: 'border-b border-b-gray-200', headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', + 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', }} /> From 23e8487a9702733fc8651bef9c37cfcbcdf4cb84 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 15:21:16 +0700 Subject: [PATCH 32/47] refactor(FE): Show zero values in RecordingForm --- .../recording/form/RecordingForm.tsx | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index c418c5b2..46fba9af 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1640,15 +1640,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
@@ -1716,8 +1709,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { @@ -1824,17 +1807,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { From d786b7b5ba8795565aedad172554419f4f0c3160 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 15:41:11 +0700 Subject: [PATCH 33/47] feat(FE): Add warehouse display and standard detail modals --- .../recording/form/RecordingForm.tsx | 198 +++++++++++++++++- 1 file changed, 196 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 46fba9af..13267c22 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -20,7 +20,7 @@ import SelectInput, { import CheckboxInput from '@/components/input/CheckboxInput'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; -import { useModal } from '@/components/Modal'; +import Modal, { useModal } from '@/components/Modal'; import AlertErrorList from '@/components/helper/form/FormErrors'; import { @@ -121,6 +121,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const approveModal = useModal(); const rejectModal = useModal(); const deleteModal = useModal(); + const fcrStandardModal = useModal(); + const productionStandardModal = useModal(); const isRecordingApproved = useCallback((recording?: Recording) => { return ( @@ -1565,9 +1567,20 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { : '-'}

+
+ Gudang +

+ {initialValues.warehouse?.name || '-'} +

+
Hari -

Hari ke-{initialValues.day}

+

+ Hari ke-{initialValues.day} (Minggu ke- + {initialValues.project_flock?.production_standart?.week || + '-'} + ) +

Kategori @@ -1604,6 +1617,41 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {

+
+ Standard FCR +
+ fcrStandardModal.openModal()} + > + {initialValues.project_flock?.fcr?.name || '-'} + +
+
+
+ + Standard Produksi + +
+ productionStandardModal.openModal()} + > + {initialValues.project_flock?.production_standart + ?.name || '-'} + +
+
)} @@ -2663,6 +2711,152 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )} )} + + {/* FCR Standard Modal */} + +
+ {/* Modal Header */} +
+
+ +

Detail Standard FCR

+
+ +
+
+
+ Nama Standard +

+ {initialValues?.project_flock?.fcr?.name || '-'} +

+
+
+ FCR Standard +

+ {initialValues?.project_flock?.fcr?.fcr_std != null + ? formatNumber( + initialValues?.project_flock?.fcr?.fcr_std || 0 + ) + : '-'} +

+
+
+
+
+ + {/* Production Standard Modal */} + +
+ {/* Modal Header */} +
+
+ +

Detail Standard Produksi

+
+ +
+
+
+ Nama Standard +

+ {initialValues?.project_flock?.production_standart?.name || '-'} +

+
+
+ Minggu +

+ {initialValues?.project_flock?.production_standart?.week || '-'} +

+
+
+ + Hen Day Standard (%) + +

+ {initialValues?.project_flock?.production_standart + ?.hen_day_std != null + ? `${initialValues?.project_flock?.production_standart?.hen_day_std}%` + : '-'} +

+
+
+ + Hen House Standard (%) + +

+ {initialValues?.project_flock?.production_standart + ?.hen_house_std != null + ? `${initialValues?.project_flock?.production_standart?.hen_house_std}%` + : '-'} +

+
+
+ + Feed Intake Standard (KG) + +

+ {initialValues?.project_flock?.production_standart + ?.feed_intake_std != null + ? formatNumber( + initialValues?.project_flock?.production_standart + ?.feed_intake_std || 0 + ) + : '-'} +

+
+
+ Egg Mass Standard +

+ {initialValues?.project_flock?.production_standart + ?.egg_mass_std != null + ? formatNumber( + initialValues?.project_flock?.production_standart + ?.egg_mass_std || 0 + ) + : '-'} +

+
+
+ + Egg Weight Standard (KG) + +

+ {initialValues?.project_flock?.production_standart + ?.egg_weight_std != null + ? formatNumber( + initialValues?.project_flock?.production_standart + ?.egg_weight_std || 0 + ) + : '-'} +

+
+
+
+
); }; From 68f9e27b5f2c930709783006bbba233fa3f329b4 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 15:48:18 +0700 Subject: [PATCH 34/47] refactor(FE): Reorder fields in RecordingForm --- .../recording/form/RecordingForm.tsx | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 13267c22..a644734e 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1468,6 +1468,105 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }} >
+
+ Lokasi +

+ {projectFlockKandangLookup?.project_flock?.location + ?.name || + projectFlockKandangDetail?.project_flock?.location + ?.name || + '-'} +

+
+
+ Project Flock +

+ {projectFlockKandangLookup?.project_flock?.flock_name || + projectFlockKandangDetail?.project_flock?.flock_name || + '-'} +

+
+
+ Kandang +

+ {projectFlockKandangLookup?.kandang?.name || + projectFlockKandangDetail?.kandang?.name || + '-'} +

+
+
+ Kategori +

+ + {initialValues.project_flock?.project_flock_category} + +

+
+
+ Gudang +

+ {initialValues.warehouse?.name || '-'} +

+
+
+ + Jumlah Ayam Saat Ini + +

+ {initialValues.project_flock?.total_chick_qty + ? formatNumber( + initialValues.project_flock.total_chick_qty + ) + : '-'} +

+
+
+ Periode +

+ + Periode{' '} + {projectFlockKandangLookup?.project_flock?.period || + projectFlockKandangDetail?.project_flock?.period || + '-'} + +

+
+
+ + Tanggal Recording + +

+ {formatDate( + initialValues.record_datetime || '', + 'DD MMMM YYYY' + )} +

+
+
+ Hari +

+ Hari ke-{initialValues.day} (Minggu ke- + {initialValues.project_flock?.production_standart?.week || + '-'} + ) +

+
{initialValues.approval && (
@@ -1518,105 +1617,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} -
- Lokasi -

- {projectFlockKandangLookup?.project_flock?.location - ?.name || - projectFlockKandangDetail?.project_flock?.location - ?.name || - '-'} -

-
-
- Project Flock -

- {projectFlockKandangLookup?.project_flock?.flock_name || - projectFlockKandangDetail?.project_flock?.flock_name || - '-'} -

-
-
- Kandang -

- {projectFlockKandangLookup?.kandang?.name || - projectFlockKandangDetail?.kandang?.name || - '-'} -

-
-
- - Tanggal Recording - -

- {formatDate( - initialValues.record_datetime || '', - 'DD MMMM YYYY' - )} -

-
-
- - Jumlah Ayam Saat Ini - -

- {initialValues.project_flock?.total_chick_qty - ? formatNumber( - initialValues.project_flock.total_chick_qty - ) - : '-'} -

-
-
- Gudang -

- {initialValues.warehouse?.name || '-'} -

-
-
- Hari -

- Hari ke-{initialValues.day} (Minggu ke- - {initialValues.project_flock?.production_standart?.week || - '-'} - ) -

-
-
- Kategori -

- - {initialValues.project_flock?.project_flock_category} - -

-
-
- Periode -

- - Periode{' '} - {projectFlockKandangLookup?.project_flock?.period || - projectFlockKandangDetail?.project_flock?.period || - '-'} - -

-
Standard FCR
From 0de4f9d7450b815a051224d9e31c21094628665b Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:07:17 +0700 Subject: [PATCH 35/47] feat(FE): Display FCR standards table in RecordingForm --- .../recording/form/RecordingForm.tsx | 100 +++++++++++++++--- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 40c2e64f..3ea4ae42 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -22,12 +22,16 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import Modal, { useModal } from '@/components/Modal'; import AlertErrorList from '@/components/helper/form/FormErrors'; +import Table from '@/components/Table'; +import { type ColumnDef } from '@tanstack/react-table'; import { ProjectFlockKandangApi, RecordingApi, ProjectFlockApi, } from '@/services/api/production'; +import { FcrApi } from '@/services/api/master-data'; +import { FcrWithStandards, FcrStandard } from '@/types/api/master-data/fcr'; import { LocationApi } from '@/services/api/master-data'; import { ProductWarehouseApi } from '@/services/api/inventory'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; @@ -76,6 +80,24 @@ interface RecordingFormProps { initialValues?: Recording; } +const fcrStandardColumns: ColumnDef[] = [ + { + accessorKey: 'weight', + header: 'Weight', + cell: (props) => formatNumber(props.getValue() as number), + }, + { + accessorKey: 'fcr_number', + header: 'FCR Number', + cell: (props) => formatNumber(props.getValue() as number), + }, + { + accessorKey: 'mortality', + header: 'Mortality', + cell: (props) => formatNumber(props.getValue() as number), + }, +]; + const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== HOOKS & ROUTER ===== const router = useRouter(); @@ -124,6 +146,42 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const fcrStandardModal = useModal(); const productionStandardModal = useModal(); + const [fcrStandards, setFcrStandards] = useState([]); + + const [isFcrModalOpen, setIsFcrModalOpen] = useState(false); + + useEffect(() => { + const checkFcrModalOpen = () => { + const isOpen = fcrStandardModal.ref.current?.open || false; + setIsFcrModalOpen(isOpen); + }; + + checkFcrModalOpen(); + + const observer = new MutationObserver(checkFcrModalOpen); + if (fcrStandardModal.ref.current) { + observer.observe(fcrStandardModal.ref.current, { + attributes: true, + attributeFilter: ['open'], + }); + } + + return () => observer.disconnect(); + }, [fcrStandardModal.ref]); + + const { data: fcr, isLoading: isLoadingFcrStandards } = useSWR( + isFcrModalOpen && initialValues?.project_flock?.fcr?.id + ? `fcr-detail-${initialValues.project_flock.fcr.id}` + : null, + () => FcrApi.getSingle(initialValues!.project_flock!.fcr!.id!) + ); + + useEffect(() => { + if (fcr?.status === 'success') { + setFcrStandards((fcr.data as FcrWithStandards).fcr_standards || []); + } + }, [fcr]); + const isRecordingApproved = useCallback((recording?: Recording) => { return ( recording?.approval?.action === 'APPROVED' && @@ -2735,23 +2793,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
-
-
- Nama Standard -

- {initialValues?.project_flock?.fcr?.name || '-'} +

+ {isLoadingFcrStandards ? ( +
+ +
+ ) : fcrStandards.length > 0 ? ( + + data={fcrStandards} + columns={fcrStandardColumns} + className={{ + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + ) : ( +

+ Tidak ada data FCR standards

-
-
- FCR Standard -

- {initialValues?.project_flock?.fcr?.fcr_std != null - ? formatNumber( - initialValues?.project_flock?.fcr?.fcr_std || 0 - ) - : '-'} -

-
+ )}
From 4285e2e26963eefd254ea2c8dbf21c6429e83410 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:16:32 +0700 Subject: [PATCH 36/47] refactor(FE): Enable backdrop close and set FCR table pageSize --- .../pages/production/recording/form/RecordingForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 3ea4ae42..67dae969 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -2773,6 +2773,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* FCR Standard Modal */} {
-
+
{isLoadingFcrStandards ? (
@@ -2802,6 +2803,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { data={fcrStandards} columns={fcrStandardColumns} + pageSize={100} className={{ tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto text-sm', From c7b4361cb610d4cbf40b17f17d9707bb1f1fb6a0 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:24:45 +0700 Subject: [PATCH 37/47] feat(FE): Add Production Standard modal and table --- .../recording/form/RecordingForm.tsx | 242 ++++++++++++------ 1 file changed, 166 insertions(+), 76 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 67dae969..435150d9 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -30,8 +30,12 @@ import { RecordingApi, ProjectFlockApi, } from '@/services/api/production'; -import { FcrApi } from '@/services/api/master-data'; +import { FcrApi, ProductionStandardApi } from '@/services/api/master-data'; import { FcrWithStandards, FcrStandard } from '@/types/api/master-data/fcr'; +import { + ProductionStandard, + StandardDetails, +} from '@/types/api/master-data/production-standard'; import { LocationApi } from '@/services/api/master-data'; import { ProductWarehouseApi } from '@/services/api/inventory'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; @@ -98,6 +102,94 @@ const fcrStandardColumns: ColumnDef[] = [ }, ]; +const productionStandardColumns: ColumnDef[] = [ + { + accessorKey: 'week', + header: 'Minggu', + cell: (props) => `Minggu ${props.getValue() as number}`, + }, + { + accessorKey: 'growth_standard_detail.target_mean_bw', + header: 'Target Mean BW (gram)', + cell: (props) => + formatNumber( + (props.row.original.growth_standard_detail?.target_mean_bw as number) || + 0 + ), + }, + { + accessorKey: 'growth_standard_detail.max_depletion', + header: 'Max Depletion (%)', + cell: (props) => + `${ + (props.row.original.growth_standard_detail?.max_depletion as number) || + 0 + }%`, + }, + { + accessorKey: 'growth_standard_detail.min_uniformity', + header: 'Min Uniformity (%)', + cell: (props) => + `${ + (props.row.original.growth_standard_detail?.min_uniformity as number) || + 0 + }%`, + }, + { + accessorKey: 'growth_standard_detail.feed_intake', + header: 'Feed Intake (gram)', + cell: (props) => + formatNumber( + (props.row.original.growth_standard_detail?.feed_intake as number) || 0 + ), + }, + { + accessorKey: 'egg_production_standard_detail.target_hen_day_production', + header: 'Target Hen Day (%)', + cell: (props) => + `${ + (props.row.original.egg_production_standard_detail + ?.target_hen_day_production as number) || 0 + }%`, + }, + { + accessorKey: 'egg_production_standard_detail.target_hen_house_production', + header: 'Target Hen House (%)', + cell: (props) => + `${ + (props.row.original.egg_production_standard_detail + ?.target_hen_house_production as number) || 0 + }%`, + }, + { + accessorKey: 'egg_production_standard_detail.target_egg_weight', + header: 'Target Egg Weight (gram)', + cell: (props) => + formatNumber( + (props.row.original.egg_production_standard_detail + ?.target_egg_weight as number) || 0 + ), + }, + { + accessorKey: 'egg_production_standard_detail.target_egg_mass', + header: 'Target Egg Mass (gram)', + cell: (props) => + formatNumber( + (props.row.original.egg_production_standard_detail + ?.target_egg_mass as number) || 0 + ), + }, + { + accessorKey: 'egg_production_standard_detail.standard_fcr', + header: 'Standard FCR', + cell: (props) => + formatNumber( + (props.row.original.egg_production_standard_detail + ?.standard_fcr as number) || 0 + ), + }, +]; + const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== HOOKS & ROUTER ===== const router = useRouter(); @@ -147,8 +239,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const productionStandardModal = useModal(); const [fcrStandards, setFcrStandards] = useState([]); + const [productionStandards, setProductionStandards] = + useState(null); const [isFcrModalOpen, setIsFcrModalOpen] = useState(false); + const [isProductionStandardModalOpen, setIsProductionStandardModalOpen] = + useState(false); useEffect(() => { const checkFcrModalOpen = () => { @@ -169,6 +265,25 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return () => observer.disconnect(); }, [fcrStandardModal.ref]); + useEffect(() => { + const checkProductionStandardModalOpen = () => { + const isOpen = productionStandardModal.ref.current?.open || false; + setIsProductionStandardModalOpen(isOpen); + }; + + checkProductionStandardModalOpen(); + + const observer = new MutationObserver(checkProductionStandardModalOpen); + if (productionStandardModal.ref.current) { + observer.observe(productionStandardModal.ref.current, { + attributes: true, + attributeFilter: ['open'], + }); + } + + return () => observer.disconnect(); + }, [productionStandardModal.ref]); + const { data: fcr, isLoading: isLoadingFcrStandards } = useSWR( isFcrModalOpen && initialValues?.project_flock?.fcr?.id ? `fcr-detail-${initialValues.project_flock.fcr.id}` @@ -182,6 +297,26 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } }, [fcr]); + const { data: productionStandard, isLoading: isLoadingProductionStandards } = + useSWR( + isProductionStandardModalOpen && + initialValues?.project_flock?.production_standart?.id + ? `production-standard-detail-${initialValues.project_flock.production_standart.id}` + : null, + () => + ProductionStandardApi.getSingle( + initialValues!.project_flock!.production_standart!.id! + ) + ); + + useEffect(() => { + if (productionStandard?.status === 'success') { + setProductionStandards( + productionStandard.data as ProductionStandard | null + ); + } + }, [productionStandard]); + const isRecordingApproved = useCallback((recording?: Recording) => { return ( recording?.approval?.action === 'APPROVED' && @@ -2828,10 +2963,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* Production Standard Modal */}
@@ -2849,81 +2985,35 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
-
-
- Nama Standard -

- {initialValues?.project_flock?.production_standart?.name || '-'} +

+ {isLoadingProductionStandards ? ( +
+ +
+ ) : productionStandards?.details && + productionStandards.details.length > 0 ? ( + + data={productionStandards.details} + columns={productionStandardColumns} + pageSize={100} + className={{ + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + ) : ( +

+ Tidak ada data Production standards

-
-
- Minggu -

- {initialValues?.project_flock?.production_standart?.week || '-'} -

-
-
- - Hen Day Standard (%) - -

- {initialValues?.project_flock?.production_standart - ?.hen_day_std != null - ? `${initialValues?.project_flock?.production_standart?.hen_day_std}%` - : '-'} -

-
-
- - Hen House Standard (%) - -

- {initialValues?.project_flock?.production_standart - ?.hen_house_std != null - ? `${initialValues?.project_flock?.production_standart?.hen_house_std}%` - : '-'} -

-
-
- - Feed Intake Standard (KG) - -

- {initialValues?.project_flock?.production_standart - ?.feed_intake_std != null - ? formatNumber( - initialValues?.project_flock?.production_standart - ?.feed_intake_std || 0 - ) - : '-'} -

-
-
- Egg Mass Standard -

- {initialValues?.project_flock?.production_standart - ?.egg_mass_std != null - ? formatNumber( - initialValues?.project_flock?.production_standart - ?.egg_mass_std || 0 - ) - : '-'} -

-
-
- - Egg Weight Standard (KG) - -

- {initialValues?.project_flock?.production_standart - ?.egg_weight_std != null - ? formatNumber( - initialValues?.project_flock?.production_standart - ?.egg_weight_std || 0 - ) - : '-'} -

-
+ )}
From 0a5414a3ac1dc4272a96ef60fc8c5d87c870e2e8 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:28:32 +0700 Subject: [PATCH 38/47] refactor(FE): Add bottom margin to production standards text --- .../pages/production/recording/form/RecordingForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 435150d9..9dd3df1f 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -3010,7 +3010,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }} /> ) : ( -

+

Tidak ada data Production standards

)} From 76c1b2f62885dffe4a65f8265ec89e22e9a64146 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:35:32 +0700 Subject: [PATCH 39/47] refactor(FE): Derive current total chick qty from flock data --- .../recording/form/RecordingForm.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 9dd3df1f..32c8c4c6 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -725,7 +725,26 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [existingRecordings, today]); const currentTotalChickQty = useMemo(() => { - if (!isResponseSuccess(existingRecordings) || !selectedKandang) return null; + if (type === 'add' && projectFlockKandangLookup) { + return projectFlockKandangLookup.available_quantity || null; + } + + if ((type === 'edit' || type === 'detail') && projectFlockKandangDetail) { + if ( + projectFlockKandangDetail.available_qtys && + projectFlockKandangDetail.available_qtys.length > 0 + ) { + return projectFlockKandangDetail.available_qtys.reduce( + (sum, item) => sum + (item.available_qty || 0), + 0 + ); + } + return null; + } + + if (!isResponseSuccess(existingRecordings) || !selectedKandang) { + return null; + } let projectFlockKandangId: number | undefined; @@ -745,6 +764,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return recording?.project_flock.total_chick_qty || null; }, [ + type, existingRecordings, selectedKandang, projectFlockKandangLookup, From 4a9cbdc219573dd4a4889014ceea222ff45a4bb0 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:41:24 +0700 Subject: [PATCH 40/47] refactor(FE): Use initialValues total_chick_qty for edit/detail --- .../production/recording/form/RecordingForm.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 32c8c4c6..5312c0be 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -729,17 +729,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return projectFlockKandangLookup.available_quantity || null; } - if ((type === 'edit' || type === 'detail') && projectFlockKandangDetail) { - if ( - projectFlockKandangDetail.available_qtys && - projectFlockKandangDetail.available_qtys.length > 0 - ) { - return projectFlockKandangDetail.available_qtys.reduce( - (sum, item) => sum + (item.available_qty || 0), - 0 - ); - } - return null; + if ((type === 'edit' || type === 'detail') && initialValues) { + return initialValues.project_flock?.total_chick_qty || null; } if (!isResponseSuccess(existingRecordings) || !selectedKandang) { @@ -765,6 +756,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return recording?.project_flock.total_chick_qty || null; }, [ type, + initialValues, existingRecordings, selectedKandang, projectFlockKandangLookup, From 8fe51c976bd4e4372271dcc7a5fed9ae67b7d027 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 17:29:09 +0700 Subject: [PATCH 41/47] refactor(FE): Show additional flock info in recording form --- .../recording/form/RecordingForm.tsx | 48 +++++++++++++++++++ src/types/api/production/project-flock.d.ts | 2 + 2 files changed, 50 insertions(+) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 5312c0be..b06fb172 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1658,6 +1658,54 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { />
+ + {/* Additional Information Fields */} + {(projectFlockKandangLookup || projectFlockKandangDetail) && ( +
+
+ Gudang +

+ {projectFlockKandangLookup?.warehouse?.name || + initialValues?.warehouse?.name || + '-'} +

+
+
+ Umur +

+ {nextDayRecording + ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})` + : initialValues?.day + ? `Hari ke-${initialValues.day} (Minggu ke-${Math.ceil(initialValues.day / 7)})` + : '-'} +

+
+
+ Standard FCR +

+ {projectFlockKandangLookup?.project_flock?.fcr?.name || + initialValues?.project_flock?.fcr?.name || + '-'} +

+
+
+ + Standard Produksi + +

+ {projectFlockKandangLookup?.project_flock + ?.production_standard_id + ? `ID: ${projectFlockKandangLookup.project_flock.production_standard_id}` + : initialValues?.project_flock?.production_standart + ?.name || '-'} +

+
+
+ )} )} diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 66cc39ed..dcc1a348 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -6,6 +6,7 @@ import { Location } from '@/types/api/master-data/location'; import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import { Nonstock } from '@/types/api/master-data/nonstock'; import { ProductionStandard } from '@/types/api/master-data/production-standard'; +import { Warehouse } from '@/types/api/master-data/warehouse'; export type BaseProjectFlock = { id: number; @@ -71,6 +72,7 @@ export type ProjectFlockKandangLookup = { kandang_id: number; kandang: Kandang; project_flock: ProjectFlock; + warehouse: Warehouse; quantity: number; available_quantity?: number; population: number; From 7d3a4c1ecc32585ec0134f0a9610aa5f58fb6fbf Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 17:47:03 +0700 Subject: [PATCH 42/47] refactor(FE): Use memoized IDs and badges for standards --- .../recording/form/RecordingForm.tsx | 120 +++++++++++------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index b06fb172..b45f72d0 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -284,39 +284,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return () => observer.disconnect(); }, [productionStandardModal.ref]); - const { data: fcr, isLoading: isLoadingFcrStandards } = useSWR( - isFcrModalOpen && initialValues?.project_flock?.fcr?.id - ? `fcr-detail-${initialValues.project_flock.fcr.id}` - : null, - () => FcrApi.getSingle(initialValues!.project_flock!.fcr!.id!) - ); - - useEffect(() => { - if (fcr?.status === 'success') { - setFcrStandards((fcr.data as FcrWithStandards).fcr_standards || []); - } - }, [fcr]); - - const { data: productionStandard, isLoading: isLoadingProductionStandards } = - useSWR( - isProductionStandardModalOpen && - initialValues?.project_flock?.production_standart?.id - ? `production-standard-detail-${initialValues.project_flock.production_standart.id}` - : null, - () => - ProductionStandardApi.getSingle( - initialValues!.project_flock!.production_standart!.id! - ) - ); - - useEffect(() => { - if (productionStandard?.status === 'success') { - setProductionStandards( - productionStandard.data as ProductionStandard | null - ); - } - }, [productionStandard]); - const isRecordingApproved = useCallback((recording?: Recording) => { return ( recording?.approval?.action === 'APPROVED' && @@ -465,6 +432,47 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangLookupData.data : undefined; + const fcrId = useMemo(() => { + if (type === 'add') { + return projectFlockKandangLookup?.project_flock?.fcr?.id; + } + return initialValues?.project_flock?.fcr?.id; + }, [type, projectFlockKandangLookup, initialValues]); + + const { data: fcr, isLoading: isLoadingFcrStandards } = useSWR( + isFcrModalOpen && fcrId ? `fcr-detail-${fcrId}` : null, + () => FcrApi.getSingle(fcrId!) + ); + + useEffect(() => { + if (fcr?.status === 'success') { + setFcrStandards((fcr.data as FcrWithStandards).fcr_standards || []); + } + }, [fcr]); + + const productionStandardId = useMemo(() => { + if (type === 'add') { + return projectFlockKandangLookup?.project_flock?.production_standard_id; + } + return initialValues?.project_flock?.production_standart?.id; + }, [type, projectFlockKandangLookup, initialValues]); + + const { data: productionStandard, isLoading: isLoadingProductionStandards } = + useSWR( + isProductionStandardModalOpen && productionStandardId + ? `production-standard-detail-${productionStandardId}` + : null, + () => ProductionStandardApi.getSingle(productionStandardId!) + ); + + useEffect(() => { + if (productionStandard?.status === 'success') { + setProductionStandards( + productionStandard.data as ProductionStandard | null + ); + } + }, [productionStandard]); + const projectFlockKandangDetailUrl = useMemo(() => { if ( type === 'add' || @@ -1686,23 +1694,45 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Standard FCR -

- {projectFlockKandangLookup?.project_flock?.fcr?.name || - initialValues?.project_flock?.fcr?.name || - '-'} -

+
+ fcrStandardModal.openModal()} + > + {projectFlockKandangLookup?.project_flock?.fcr?.name || + initialValues?.project_flock?.fcr?.name || + '-'} + +
Standard Produksi -

- {projectFlockKandangLookup?.project_flock - ?.production_standard_id - ? `ID: ${projectFlockKandangLookup.project_flock.production_standard_id}` - : initialValues?.project_flock?.production_standart - ?.name || '-'} -

+
+ productionStandardModal.openModal()} + > + {productionStandards?.name || + initialValues?.project_flock?.production_standart + ?.name || + (projectFlockKandangLookup?.project_flock + ?.production_standard_id + ? `Production Standard ${projectFlockKandangLookup.project_flock.production_standard_id}` + : '-')} + +
)} From 0a24c4541f7b5a003debe66437976567bc5bddfd Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 17:53:18 +0700 Subject: [PATCH 43/47] refactor(FE): Make feed and doc supplier arrays nullable --- src/types/api/report/hpp-per-kandang.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/api/report/hpp-per-kandang.d.ts b/src/types/api/report/hpp-per-kandang.d.ts index 1b3f7fcd..208a2cdb 100644 --- a/src/types/api/report/hpp-per-kandang.d.ts +++ b/src/types/api/report/hpp-per-kandang.d.ts @@ -14,8 +14,8 @@ export type HppPerKandangRow = { egg_production_kg: number; egg_hpp_rp_per_kg: number; egg_value_rp: number; - feed_suppliers: Supplier[]; - doc_suppliers: Supplier[]; + feed_suppliers: Supplier[] | null; + doc_suppliers: Supplier[] | null; average_doc_price_rp: number; }; From 87bf474cf60db156c2539bb35df3e52e72af5f66 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 18:01:15 +0700 Subject: [PATCH 44/47] refactor(FE): Add customer address as card subtitle --- src/components/pages/report/finance/tab/CustomerPaymentTab.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 3254cf03..ef748b5f 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -743,11 +743,14 @@ const CustomerPaymentTab = () => { Date: Thu, 15 Jan 2026 19:29:03 +0700 Subject: [PATCH 45/47] refactor(FE): Make vehicle/expedition/transport fields optional --- .../order/PurchaseOrderAcceptApprovalForm.tsx | 30 ++++-------- .../form/order/PurchaseOrderForm.schema.ts | 46 +++++++++++-------- src/types/api/purchase/purchase.d.ts | 8 ++-- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx index 2f619778..8585a091 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx @@ -185,8 +185,8 @@ const PurchaseOrderAcceptApprovalForm = ({ purchase_item_id: formItem.purchase_item_id || 0, received_date: formItem.received_date || '', travel_number: formItem.travel_number || '', - vehicle_number: formItem.vehicle_number || '', - expedition_vendor_id: formItem.expedition_vendor_id || 0, + vehicle_number: formItem.vehicle_number || null, + expedition_vendor_id: formItem.expedition_vendor_id || null, received_qty: typeof formItem.received_qty === 'string' ? parseFloat(formItem.received_qty) || 0 @@ -194,10 +194,13 @@ const PurchaseOrderAcceptApprovalForm = ({ transport_per_item: typeof formItem.transport_per_item === 'string' ? parseFloat(formItem.transport_per_item) || 0 - : formItem.transport_per_item || 0, + : formItem.transport_per_item || null, }; }) || [], - travel_documents: values.travel_documents || [], + travel_documents: + values.travel_documents + ?.filter((file): file is File => file instanceof File) + .filter(Boolean) || undefined, }; switch (type) { @@ -405,22 +408,13 @@ const PurchaseOrderAcceptApprovalForm = ({ Dokumen Surat Jalan * -
- + + - + @@ -538,7 +532,6 @@ const PurchaseOrderAcceptApprovalForm = ({
+
+ {formatCurrency(row.original.accounts_receivable)} +
+
From efde742518ef38d60ba0f33ff360c83da0ab0382 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 17:03:40 +0700 Subject: [PATCH 09/47] refactor(FE): Highlight negative accounts receivable values --- .../report/finance/tab/CustomerPaymentTab.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 7d34a08f..951bc8f1 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -470,11 +470,17 @@ const CustomerPaymentTab = () => { cell: (props) => { const value = props.row.original.accounts_receivable; return ( -
{formatCurrency(value)}
+
+ {formatCurrency(value)} +
); }, footer: () => ( -
+
{formatCurrency(summary.total_accounts_receivable) || '-'}
), @@ -782,8 +788,14 @@ const CustomerPaymentTab = () => { className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap' colSpan={13} >
-
+
+
{formatCurrency(row.original.accounts_receivable)}
Rekapitulasi per rentang bobot @@ -747,12 +677,6 @@ const HppPerKandangTab = () => { {formatNumber(item.avg_weight_kg)} - {formatNumber(item.remaining_chicken_birds)} - - {formatNumber(item.remaining_chicken_weight_kg)} - {formatNumber(item.egg_production_pieces)} {formatCurrency(item.average_doc_price_rp)} - {formatCurrency(item.egg_value_rp)} - {formatCurrency(item.hpp_rp)} {formatCurrency(item.egg_hpp_rp_per_kg)} - {formatCurrency(item.remaining_value_rp)} + {formatCurrency(item.egg_value_rp)}
FCR - {initialValues.fcr_value && - initialValues.fcr_value > 0 + {initialValues.fcr_value != null ? formatNumber(initialValues.fcr_value) : '-'} - {initialValues.project_flock?.fcr?.fcr_std && - initialValues.project_flock?.fcr?.fcr_std > 0 + {initialValues.project_flock?.fcr?.fcr_std != null ? formatNumber( initialValues.project_flock?.fcr?.fcr_std ) @@ -1659,17 +1657,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Feed Intake (KG) - {initialValues.feed_intake && - initialValues.feed_intake > 0 + {initialValues.feed_intake != null ? formatNumber(initialValues.feed_intake) : '-'} {initialValues.project_flock?.production_standart - ?.feed_intake_std && - initialValues.project_flock?.production_standart - ?.feed_intake_std > 0 + ?.feed_intake_std != null ? formatNumber( initialValues.project_flock?.production_standart ?.feed_intake_std @@ -1696,8 +1691,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Deplesi Kumulatif - {initialValues.cum_depletion_rate && - initialValues.cum_depletion_rate > 0 + {initialValues.cum_depletion_rate != null ? `${initialValues.cum_depletion_rate.toFixed(2)}%` : '-'} @@ -1706,8 +1700,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Total Depletion - {initialValues.total_depletion_qty && - initialValues.total_depletion_qty > 0 + {initialValues.total_depletion_qty != null ? formatNumber(initialValues.total_depletion_qty) : '-'}
Total Ayam - {initialValues.project_flock?.total_chick_qty && - initialValues.project_flock?.total_chick_qty > 0 + {initialValues.project_flock?.total_chick_qty != null ? formatNumber( initialValues.project_flock?.total_chick_qty ) @@ -1759,17 +1751,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Egg Mass - {initialValues.egg_mass && - initialValues.egg_mass > 0 + {initialValues.egg_mass != null ? formatNumber(initialValues.egg_mass) : '-'} {initialValues.project_flock?.production_standart - ?.egg_mass_std && - initialValues.project_flock?.production_standart - ?.egg_mass_std > 0 + ?.egg_mass_std != null ? formatNumber( initialValues.project_flock ?.production_standart?.egg_mass_std @@ -1783,17 +1772,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.egg_weight && - initialValues.egg_weight > 0 + {initialValues.egg_weight != null ? formatNumber(initialValues.egg_weight) : '-'} {initialValues.project_flock?.production_standart - ?.egg_weight_std && - initialValues.project_flock?.production_standart - ?.egg_weight_std > 0 + ?.egg_weight_std != null ? formatNumber( initialValues.project_flock ?.production_standart?.egg_weight_std @@ -1805,17 +1791,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Hen Day - {initialValues.hen_day && - initialValues.hen_day > 0 + {initialValues.hen_day != null ? formatNumber(initialValues.hen_day) : '-'} {initialValues.project_flock?.production_standart - ?.hen_day_std !== undefined && - initialValues.project_flock?.production_standart - ?.hen_day_std > 0 + ?.hen_day_std != null ? `${initialValues.project_flock?.production_standart?.hen_day_std}%` : '-'} Hen House - {initialValues.hen_house && - initialValues.hen_house > 0 + {initialValues.hen_house != null ? formatNumber(initialValues.hen_house) : '-'} {initialValues.project_flock?.production_standart - ?.hen_house_std !== undefined && - initialValues.project_flock?.production_standart - ?.hen_house_std > 0 + ?.hen_house_std != null ? `${initialValues.project_flock?.production_standart?.hen_house_std}%` : '-'} - Nomor Kendaraan - * - - Vendor Ekspedisi - * - Nomor KendaraanVendor Ekspedisi Jumlah Diterima * - Transport/Item - * - Transport/Item
@@ -684,7 +675,6 @@ const PurchaseOrderAcceptApprovalForm = ({
0); } - ) - .typeError('Vendor ekspedisi harus dipilih!'), + ), received_qty: Yup.mixed() .required('Jumlah diterima wajib diisi!') .test( @@ -217,13 +221,14 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema() - .required('Biaya transport per item wajib diisi!') + .nullable() + .optional() .test( 'is-valid-transport-per-item', 'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!', function (value) { if (value === '' || value === null || value === undefined) - return false; + return true; const numValue = typeof value === 'string' ? parseFloat(value) : value; return !isNaN(numValue) && numValue >= 0; @@ -389,16 +394,17 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema() - .required('Dokumen surat jalan wajib diupload!') + .nullable() + .optional() .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { if (!value) return true; if (value instanceof File) return value.size <= 5 * 1024 * 1024; return true; }) ) - .required('Dokumen surat jalan wajib diupload!') - .min(1, 'Minimal upload 1 dokumen surat jalan!') - .typeError('Dokumen surat jalan wajib diupload!'), + .nullable() + .optional() + .typeError('Dokumen surat jalan harus berupa array!'), }); export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType = diff --git a/src/types/api/purchase/purchase.d.ts b/src/types/api/purchase/purchase.d.ts index 34798ac3..9ad59f8b 100644 --- a/src/types/api/purchase/purchase.d.ts +++ b/src/types/api/purchase/purchase.d.ts @@ -120,12 +120,12 @@ export type CreateAcceptApprovalRequestPayload = { purchase_item_id: number; received_date: string; travel_number: string; - vehicle_number: string; - expedition_vendor_id: number; + vehicle_number?: string | null; + expedition_vendor_id?: number | null; received_qty: number; - transport_per_item: number; + transport_per_item?: number | null; }[]; - travel_documents?: File[]; + travel_documents?: File[] | null; }; export type DeletePurchaseRequestItemPayload = { From 45700be730333f3c889d8d97e38652ba058922e8 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 19:32:09 +0700 Subject: [PATCH 46/47] refactor(FE): Improve vehicle number validation message and set expedition flag on supplier --- .../purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx | 1 + .../pages/purchase/form/order/PurchaseOrderForm.schema.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx index 8585a091..de27a169 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx @@ -160,6 +160,7 @@ const PurchaseOrderAcceptApprovalForm = ({ hasMore: hasMoreExpeditions, } = useSelect(SupplierApi.basePath, 'id', 'name', 'search', { category: 'BOP', + flag: 'EKSPEDISI', }); // ===== FORM CONFIGURATION ===== diff --git a/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts b/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts index 1ee2e054..5df7c8e6 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts +++ b/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts @@ -186,7 +186,7 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema Date: Thu, 15 Jan 2026 19:37:55 +0700 Subject: [PATCH 47/47] refactor(FE): Relax expedition vendor validation --- .../purchase/form/order/PurchaseOrderForm.schema.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts b/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts index 5df7c8e6..6a299703 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts +++ b/src/components/pages/purchase/form/order/PurchaseOrderForm.schema.ts @@ -196,16 +196,7 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema 0); - } - ), + .typeError('Vendor ekspedisi harus berupa angka!'), received_qty: Yup.mixed() .required('Jumlah diterima wajib diisi!') .test(