From a781431683b2484a3e615c114aafc52159b98847 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 13:35:56 +0700 Subject: [PATCH 001/169] refactor(FE): Rename Type column header to Jenis --- src/components/pages/closing/ClosingFinanceTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/closing/ClosingFinanceTable.tsx b/src/components/pages/closing/ClosingFinanceTable.tsx index 8c39b3fb..7eb34369 100644 --- a/src/components/pages/closing/ClosingFinanceTable.tsx +++ b/src/components/pages/closing/ClosingFinanceTable.tsx @@ -299,7 +299,7 @@ const ClosingFinanceTable = ({ }, }, { - header: 'Type', + header: 'Jenis', enableSorting: false, accessorFn: (item) => formatTitleCase(item.type || '-'), }, From 502564da0a0d1e36623531d6eb9cc9f7def63ac9 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 13:43:34 +0700 Subject: [PATCH 002/169] refactor(FE): Exclude step 6 and include step 4 in edit check --- src/app/expense/detail/edit/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/expense/detail/edit/page.tsx b/src/app/expense/detail/edit/page.tsx index e254f01d..2fb33484 100644 --- a/src/app/expense/detail/edit/page.tsx +++ b/src/app/expense/detail/edit/page.tsx @@ -38,9 +38,11 @@ const ExpenseEditPage = () => { !isLoadingExpense && isResponseSuccess(expense) && expense.data.latest_approval.step_number !== 5 && + expense.data.latest_approval.step_number !== 6 && (expense.data.latest_approval.step_number === 1 || expense.data.latest_approval.step_number === 2 || - expense.data.latest_approval.step_number === 3); + expense.data.latest_approval.step_number === 3 || + expense.data.latest_approval.step_number === 4); if (!isLoadingExpense && !isExpenseCanBeEdited) { router.back(); From 92bfef850a9eb7f10888b549a7a0600e6184c3d6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 14:31:30 +0700 Subject: [PATCH 003/169] refactor(FE): Enhance customer payment PDF export and filters --- .../export/CustomerPaymentExportPDF.tsx | 145 +++++++++++++++--- .../report/finance/tab/CustomerPaymentTab.tsx | 52 +++---- 2 files changed, 145 insertions(+), 52 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 88c556de..08efa409 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -136,41 +136,129 @@ const pdfStyles = StyleSheet.create({ backgroundColor: '#F0F0F0', fontWeight: 'bold', }, + badge: { + backgroundColor: '#1f74bf', + color: '#FFFFFF', + padding: 2, + borderRadius: 2, + fontSize: 7, + fontWeight: 'bold', + alignSelf: 'center', + marginRight: 4, + }, + badgeLunas: { + backgroundColor: '#1f74bf', + color: '#FFFFFF', + }, + badgeBelumLunas: { + backgroundColor: '#F97316', + color: '#FFFFFF', + }, + parameterBadge: { + backgroundColor: '#F5F5F5', + color: '#333333', + padding: 4, + borderRadius: 4, + fontSize: 8, + marginRight: 8, + marginBottom: 4, + }, + parameterContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + marginBottom: 8, + }, }); interface CustomerPaymentExportPDFParams { data: CustomerPaymentReport[]; + params?: { + customer_name?: string; + sales?: string; + start_date?: string; + end_date?: string; + filter_by?: string; + }; } +const getParameterText = ( + params?: CustomerPaymentExportPDFParams['params'] +) => { + const paramsText = []; + + if (params?.customer_name) { + paramsText.push(`Customer: ${params.customer_name}`); + } else { + paramsText.push('Semua Customer'); + } + + if (params?.sales) { + paramsText.push(`Sales: ${params.sales}`); + } + + if (params?.start_date && params?.end_date) { + const startDate = formatDate(params.start_date, 'DD MMM YYYY'); + const endDate = formatDate(params.end_date, 'DD MMM YYYY'); + paramsText.push(`Periode: ${startDate} - ${endDate}`); + } else if (params?.start_date) { + const startDate = formatDate(params.start_date, 'DD MMM YYYY'); + paramsText.push(`Tanggal: ${startDate}`); + } + + const currentDate = formatDate(new Date(), 'DD MMM YYYY HH:mm'); + paramsText.push(`Dicetak: ${currentDate}`); + + return paramsText; +}; + const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { return ( {params.data.map((customerReport, customerIndex) => ( - {/* Title and Customer Info */} + {/* Title and Parameters */} Laporan > Kontrol Pembayaran Customer + + + + Periode:{' '} + {params.params?.start_date + ? formatDate(params.params.start_date, 'DD MMM YYYY') + : '-'}{' '} + s.d{' '} + {params.params?.end_date + ? formatDate(params.params.end_date, 'DD MMM YYYY') + : '-'} + + + + Filter Tanggal: Tanggal DO + + + + Customer: {params.params?.customer_name || 'Semua Customer'} + + + + + Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')} + + + {customerReport.customer.name} - {customerReport.customer.address || ''} + Alamat: {customerReport.customer.address || '-'} - {customerReport.summary && ( - - Total Saldo Piutang:{' '} - {formatCurrency( - customerReport.summary.total_accounts_receivable - )} - - )} {/* Table */} @@ -181,10 +269,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { No - Tgl DO/Bayar + Tanggal DO - Tgl Realisasi + Tanggal Realisasi Aging @@ -193,16 +281,16 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Referensi - No. Polisi + No Polisi Qty - Berat (Kg) + Berat - AVG + Rata-Rata Harga Awal @@ -214,7 +302,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Harga Akhir - PPN (%) + Pajak Total @@ -223,10 +311,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Pembayaran - Saldo Piutang + Saldo - Ket + Keterangan Pengambilan @@ -304,7 +392,24 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatCurrency(item.accounts_receivable)} - {item.notes || '-'} + {item.notes ? ( + {item.notes} + ) : ( + + + {item.accounts_receivable === 0 + ? 'Lunas' + : 'Belum Lunas'} + + + )} {item.pickup_info || '-'} diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 1d8d1993..6cd0ba3d 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -46,7 +46,6 @@ const CustomerPaymentTab = () => { const [filterSales, setFilterSales] = useState([]); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); - const [filterErrors, setFilterErrors] = useState>({}); const filterModal = useModal(); @@ -75,27 +74,13 @@ const CustomerPaymentTab = () => { setFilterSales([]); setFilterStartDate(''); setFilterEndDate(''); - setFilterErrors({}); }, []); const handleApplyFilters = useCallback(() => { - const errors: Record = {}; - - if (!filterStartDate) { - errors.start_date = 'Tanggal mulai wajib diisi'; - } - if (!filterEndDate) { - errors.end_date = 'Tanggal akhir wajib diisi'; - } - - setFilterErrors(errors); - - if (Object.keys(errors).length === 0) { - setIsSubmitted(true); - setCurrentPage(1); - filterModal.closeModal(); - } - }, [filterModal, filterStartDate, filterEndDate]); + setIsSubmitted(true); + setCurrentPage(1); + filterModal.closeModal(); + }, [filterModal]); // ===== DATA FETCHING ===== const { data: customerPayment, isLoading } = useSWR( @@ -218,7 +203,22 @@ const CustomerPaymentTab = () => { return; } - await generateCustomerPaymentPDF({ data: allDataForExport }); + await generateCustomerPaymentPDF({ + data: allDataForExport, + params: { + customer_name: + filterCustomer.length > 0 + ? filterCustomer.map((c) => c.label).join(', ') + : undefined, + sales: + filterSales.length > 0 + ? filterSales.map((s) => s.label).join(', ') + : undefined, + start_date: filterStartDate || undefined, + end_date: filterEndDate || undefined, + filter_by: 'do_date', + }, + }); toast.success('PDF berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat PDF. Silakan coba lagi.'); @@ -538,15 +538,9 @@ const CustomerPaymentTab = () => { value={filterStartDate} onChange={(e) => { setFilterStartDate(e.target.value); - setFilterErrors((prev) => ({ ...prev, start_date: '' })); }} className={{ wrapper: 'w-full' }} /> - {filterErrors.start_date && ( -

- {filterErrors.start_date} -

- )}
@@ -556,15 +550,9 @@ const CustomerPaymentTab = () => { value={filterEndDate} onChange={(e) => { setFilterEndDate(e.target.value); - setFilterErrors((prev) => ({ ...prev, end_date: '' })); }} className={{ wrapper: 'w-full' }} /> - {filterErrors.end_date && ( -

- {filterErrors.end_date} -

- )}
From 60eaec261d8851ec3c40daab8b250eb85b7380ec Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 15:20:27 +0700 Subject: [PATCH 004/169] refactor(FE): Render payment notes as Badge and highlight balance --- .../report/finance/tab/CustomerPaymentTab.tsx | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 6cd0ba3d..2b2c09a2 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -2,6 +2,7 @@ import { useState, useMemo, useCallback } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Card from '@/components/Card'; +import Badge from '@/components/Badge'; import SelectInput, { useSelect, OptionType, @@ -67,6 +68,38 @@ const CustomerPaymentTab = () => { [] ); + const getPaymentStatusColor = (notes: string) => { + const normalizedValue = notes.toLowerCase(); + + if (normalizedValue === 'lunas') { + return 'bg-info/10 text-info border-info'; + } + + if (normalizedValue.includes('belum')) { + return 'bg-warning/10 text-warning border-warning'; + } + + return 'bg-gray-100 text-gray-600 border-gray-300'; + }; + + const getPaymentStatusIndicatorColor = (notes: string) => { + const normalizedValue = notes.toLowerCase(); + + if (normalizedValue === 'lunas') { + return 'bg-info'; + } + + if (normalizedValue.includes('belum')) { + return 'bg-warning'; + } + + return 'bg-gray-400'; + }; + + const getPaymentStatusText = (notes: string) => { + return notes; + }; + // ===== FILTER HANDLERS ===== const handleResetFilters = useCallback(() => { setIsSubmitted(false); @@ -435,7 +468,9 @@ const CustomerPaymentTab = () => { accessorKey: 'accounts_receivable', cell: (props) => { const value = props.row.original.accounts_receivable; - return
{formatCurrency(value)}
; + return ( +
{formatCurrency(value)}
+ ); }, footer: () => (
@@ -449,7 +484,23 @@ const CustomerPaymentTab = () => { accessorKey: 'notes', cell: (props) => { const value = props.row.original.notes; - return value || '-'; + + if (!value) { + return '-'; + } + + return ( + + {getPaymentStatusText(value)} + + ); }, }, { @@ -647,14 +698,13 @@ const CustomerPaymentTab = () => { total_accounts_receivable: 0, }; - const totalAccountsReceivable = summary.total_accounts_receivable; const tableColumns = getTableColumns(summary); return ( Date: Tue, 13 Jan 2026 15:20:36 +0700 Subject: [PATCH 005/169] feat(FE): closing report overhead per kandang --- .../pages/closing/ClosingOverheadTable.tsx | 15 +++++++++++---- src/services/api/closing.ts | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/pages/closing/ClosingOverheadTable.tsx b/src/components/pages/closing/ClosingOverheadTable.tsx index 3df0844d..ed74ca66 100644 --- a/src/components/pages/closing/ClosingOverheadTable.tsx +++ b/src/components/pages/closing/ClosingOverheadTable.tsx @@ -5,21 +5,27 @@ import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper'; import { ClosingApi } from '@/services/api/closing'; import { Overhead, OverheadTotal } from '@/types/api/closing'; import { ColumnDef } from '@tanstack/react-table'; +import { useSearchParams } from 'next/navigation'; import { useMemo } from 'react'; import useSWR from 'swr'; interface ClosingOverheadTableProps { - type?: 'detail'; projectFlockId: number; } const ClosingOverheadTable = ({ - type, projectFlockId, }: ClosingOverheadTableProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + const { data: overhead, isLoading: isLoadingOverhead } = useSWR( - `${ClosingApi.basePath}/${projectFlockId}/overhead`, - () => ClosingApi.getOverhead(projectFlockId), + `${ClosingApi.basePath}/${projectFlockId}${kandangId ? `/${kandangId}` : ''}/overhead`, + () => + ClosingApi.getOverhead( + projectFlockId, + kandangId ? Number(kandangId) : undefined + ), { keepPreviousData: true, } @@ -148,6 +154,7 @@ const ClosingOverheadTable = ({ 'whitespace-nowrap' ), }} + isLoading={isLoadingOverhead} renderFooter={ isResponseSuccess(overhead) ? overhead.data?.overheads.length > 0 diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 892fc88e..ff6a0bcb 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -131,10 +131,11 @@ export class ClosingApiService extends BaseApiService { } async getOverhead( - id: number + id: number, + kandangId?: number ): Promise | undefined> { try { - const path = `${this.basePath}/${id}/overhead`; + const path = `${this.basePath}/${id}${kandangId ? `/${kandangId}` : ''}/overhead`; return await httpClient>(path, { method: 'GET', }); From 34d7310cc928192b9e2e857865c115ff1f907880 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 15:32:39 +0700 Subject: [PATCH 006/169] refactor(FE): Style accounts receivable in red and adjust badge --- .../report/finance/export/CustomerPaymentExportPDF.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 08efa409..5adcb694 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -154,6 +154,9 @@ const pdfStyles = StyleSheet.create({ backgroundColor: '#F97316', color: '#FFFFFF', }, + textError: { + color: '#DC2626', + }, parameterBadge: { backgroundColor: '#F5F5F5', color: '#333333', @@ -389,7 +392,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatCurrency(item.payment)} - {formatCurrency(item.accounts_receivable)} + + {formatCurrency(item.accounts_receivable)} + {item.notes ? ( @@ -483,7 +488,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - + {formatCurrency( customerReport.summary.total_accounts_receivable )} From e77a43300a5a42663b32d1852c7956a1a9103739 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 16:01:30 +0700 Subject: [PATCH 007/169] refactor(FE): Nest project_flock in Recording and update UI --- .../production/recording/RecordingTable.tsx | 8 +- .../recording/form/RecordingForm.tsx | 92 ++++++++++++------- src/types/api/production/recording.d.ts | 44 ++++++--- 3 files changed, 98 insertions(+), 46 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 39b17ef7..6b10b26e 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -678,12 +678,13 @@ const RecordingTable = () => { { header: 'Nama Project', cell: (props) => - `Project ${props.row.original.project_flock_kandang_id}`, + props.row.original.project_flock?.flock_name || '-', }, { header: 'Kategori', cell: (props) => { - const category = props.row.original.project_flock_category; + const category = + props.row.original.project_flock?.project_flock_category; if (!category) return '-'; const color = category === 'LAYING' ? 'info' : 'warning'; return ( @@ -706,7 +707,8 @@ const RecordingTable = () => { { header: 'Populasi Awal', cell: (props) => - props.row.original.total_chick_qty?.toLocaleString() || '-', + props.row.original.project_flock?.total_chick_qty?.toLocaleString() || + '-', }, { header: 'Status Approval', diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 4966172c..5097fe27 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -252,9 +252,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { : undefined; const projectFlockKandangDetailUrl = useMemo(() => { - if (type === 'add' || !initialValues?.project_flock_kandang_id) return null; - return `${ProjectFlockKandangApi.basePath}/${initialValues.project_flock_kandang_id}`; - }, [type, initialValues?.project_flock_kandang_id]); + if ( + type === 'add' || + !initialValues?.project_flock?.project_flock_kandang_id + ) + return null; + return `${ProjectFlockKandangApi.basePath}/${initialValues.project_flock.project_flock_kandang_id}`; + }, [type, initialValues?.project_flock?.project_flock_kandang_id]); const { data: projectFlockKandangDetailData } = useSWR( projectFlockKandangDetailUrl, @@ -404,12 +408,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [approvedProjectFlockKandangsData]); const isLayingCategory = - initialValues?.project_flock_category === 'LAYING' || + initialValues?.project_flock?.project_flock_category === 'LAYING' || projectFlockKandangLookup?.project_flock?.category === 'LAYING' || projectFlockKandangDetail?.project_flock?.category === 'LAYING'; const isGrowingCategory = - initialValues?.project_flock_category === 'GROWING' || + initialValues?.project_flock?.project_flock_category === 'GROWING' || projectFlockKandangLookup?.project_flock?.category === 'GROWING' || projectFlockKandangDetail?.project_flock?.category === 'GROWING'; @@ -555,7 +559,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { todayRecordings.forEach((recording) => { const recordingDate = recording.record_datetime?.split('T')[0]; if (recordingDate === today) { - recordedIds.add(recording.project_flock_kandang_id); + recordedIds.add(recording.project_flock.project_flock_kandang_id); } }); @@ -1005,7 +1009,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const hasSameDayRecording = isResponseSuccess(existingRecordings) ? existingRecordings.data?.some( (recording: Recording) => - recording.project_flock_kandang_id === + recording.project_flock.project_flock_kandang_id === projectFlockKandangId && recording.day === nextDayRecording.next_day ) @@ -1543,13 +1547,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.project_flock_category} + {initialValues.project_flock?.project_flock_category}

@@ -1579,7 +1584,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {type === 'detail' && initialValues && (
{ - {initialValues.fcr_std && initialValues.fcr_std > 0 - ? formatNumber(initialValues.fcr_std) + {initialValues.project_flock?.fcr?.fcr_std && + initialValues.project_flock?.fcr?.fcr_std > 0 + ? formatNumber( + initialValues.project_flock?.fcr?.fcr_std + ) : '-'} @@ -1630,9 +1638,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.feed_intake_std && - initialValues.feed_intake_std > 0 - ? formatNumber(initialValues.feed_intake_std) + {initialValues.project_flock?.production_standart + ?.feed_intake_std && + initialValues.project_flock?.production_standart + ?.feed_intake_std > 0 + ? formatNumber( + initialValues.project_flock?.production_standart + ?.feed_intake_std + ) : '-'} @@ -1698,9 +1711,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { colSpan={2} className='text-center py-3 font-semibold' > - {initialValues.total_chick_qty && - initialValues.total_chick_qty > 0 - ? formatNumber(initialValues.total_chick_qty) + {initialValues.project_flock?.total_chick_qty && + initialValues.project_flock?.total_chick_qty > 0 + ? formatNumber( + initialValues.project_flock?.total_chick_qty + ) : '-'} @@ -1712,7 +1727,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {/* Egg Production Section - Only for LAYING category */} {type === 'detail' && initialValues && - initialValues.project_flock_category === 'LAYING' && ( + initialValues.project_flock?.project_flock_category === + 'LAYING' && (
@@ -1744,9 +1760,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.egg_mass_std && - initialValues.egg_mass_std > 0 - ? formatNumber(initialValues.egg_mass_std) + {initialValues.project_flock?.production_standart + ?.egg_mass_std && + initialValues.project_flock?.production_standart + ?.egg_mass_std > 0 + ? formatNumber( + initialValues.project_flock + ?.production_standart?.egg_mass_std + ) : '-'} @@ -1763,9 +1784,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.egg_weight_std && - initialValues.egg_weight_std > 0 - ? formatNumber(initialValues.egg_weight_std) + {initialValues.project_flock?.production_standart + ?.egg_weight_std && + initialValues.project_flock?.production_standart + ?.egg_weight_std > 0 + ? formatNumber( + initialValues.project_flock + ?.production_standart?.egg_weight_std + ) : '-'} @@ -1780,9 +1806,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.hen_day_std !== undefined && - initialValues.hen_day_std > 0 - ? `${initialValues.hen_day_std}%` + {initialValues.project_flock?.production_standart + ?.hen_day_std !== undefined && + initialValues.project_flock?.production_standart + ?.hen_day_std > 0 + ? `${initialValues.project_flock?.production_standart?.hen_day_std}%` : '-'} @@ -1797,9 +1825,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.hen_house_std !== undefined && - initialValues.hen_house_std > 0 - ? `${initialValues.hen_house_std}%` + {initialValues.project_flock?.production_standart + ?.hen_house_std !== undefined && + initialValues.project_flock?.production_standart + ?.hen_house_std > 0 + ? `${initialValues.project_flock?.production_standart?.hen_house_std}%` : '-'} diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index 1728516a..079bf045 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -1,34 +1,52 @@ import { BaseApproval, BaseMetadata, User } from '@/types/api/api-general'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; +import { Warehouse } from '@/types/api/master-data/warehouse'; + +export type ProductionStandard = { + id: number; + week: number; + name: string; + hen_day_std: number; + hen_house_std: number; + feed_intake_std: number; + max_depletion_std: number; + egg_mass_std: number; + egg_weight_std: number; +}; + +export type FCR = { + id: number; + name: string; + fcr_std: number; +}; + +export type ProjectFlock = { + project_flock_kandang_id: number; + flock_name: string; + project_flock_category: 'GROWING' | 'LAYING'; + period: number; + production_standart: ProductionStandard; + fcr: FCR; + total_chick_qty: number; +}; export type ProductionMetrics = { total_depletion_qty: number; cum_depletion_rate: number; cum_intake: number; fcr_value: number; - fcr_std?: number; - total_chick_qty: number; hen_day?: number; hen_house?: number; feed_intake?: number; - feed_intake_std?: number; egg_mass?: number; egg_weight?: number; - hen_day_std?: number; - hen_house_std?: number; - egg_mass_std?: number; - egg_weight_std?: number; - daily_gain?: number; - avg_daily_gain?: number; - cum_depletion?: number; }; export type BaseRecording = { id: number; - project_flock_kandang_id: number; + project_flock: ProjectFlock; record_datetime: string; day: number; - project_flock_category?: 'GROWING' | 'LAYING'; } & ProductionMetrics; export type RecordingDepletion = { @@ -68,6 +86,8 @@ export type Recording = BaseMetadata & BaseRecording & { approval?: BaseApproval; created_user: User; + warehouse?: Warehouse; + product_category?: 'GROWING' | 'LAYING'; depletions?: RecordingDepletion[]; stocks?: RecordingStock[]; eggs?: RecordingEgg[]; From 8481b77c90d38b061440a9a602e519b7d1d35fd3 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 16:21:41 +0700 Subject: [PATCH 008/169] refactor(FE): Adjust cumulative depletion table layout --- .../recording/form/RecordingForm.tsx | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 5097fe27..c279bde7 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1663,54 +1663,31 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- - - - - - - - - - + - + + + + - - - - - + + +
- DEPLESI KUMULATIF -
- Total - - (%) -
+ Deplesi Kumulatif - {initialValues.total_depletion_qty && - initialValues.total_depletion_qty > 0 - ? formatNumber(initialValues.total_depletion_qty) + {initialValues.cum_depletion_rate && + initialValues.cum_depletion_rate > 0 + ? `${initialValues.cum_depletion_rate.toFixed(2)}%` : '-'} - {initialValues.cum_depletion_rate && - initialValues.cum_depletion_rate > 0 - ? initialValues.cum_depletion_rate.toFixed(2) +
Total Depletion + {initialValues.total_depletion_qty && + initialValues.total_depletion_qty > 0 + ? formatNumber(initialValues.total_depletion_qty) : '-'}
- Total Ayam -
+
Total Ayam {initialValues.project_flock?.total_chick_qty && initialValues.project_flock?.total_chick_qty > 0 ? formatNumber( @@ -1718,6 +1695,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ) : '-'}
From 582b971c09b6aa394ba5dbbc3ed6db435ad6d5ce Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 13 Jan 2026 16:47:58 +0700 Subject: [PATCH 009/169] refactor(FE): Change endpoint path to reports --- src/components/pages/report/MarketingReportContent.tsx | 2 +- src/services/api/report/marketing-sale.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/pages/report/MarketingReportContent.tsx b/src/components/pages/report/MarketingReportContent.tsx index d54c935a..3ebacecb 100644 --- a/src/components/pages/report/MarketingReportContent.tsx +++ b/src/components/pages/report/MarketingReportContent.tsx @@ -33,7 +33,7 @@ const MarketingReportContent = () => { const [activeTab, setActiveTab] = useState('daily'); return ( -
+
Date: Wed, 14 Jan 2026 00:02:44 +0700 Subject: [PATCH 010/169] refactor(FE): Add record_date to recording payloads --- .../pages/production/recording/form/RecordingForm.tsx | 4 ++++ src/types/api/production/recording.d.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index c279bde7..4f9018fc 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -117,8 +117,10 @@ 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, stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, @@ -134,8 +136,10 @@ 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, stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index 079bf045..e30abe4c 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -101,6 +101,7 @@ export type NextDayRecording = { export type CreateGrowingRecordingPayload = { project_flock_kandang_id: number; + record_date: string; stocks?: { product_warehouse_id: number; qty: number; From a5d2d855725b704cc606b5264ab696d0dc72e051 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Wed, 14 Jan 2026 09:28:16 +0700 Subject: [PATCH 011/169] chore: hide AWG std and AWG act --- .../pages/closing/ClosingProductionDataTabContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/closing/ClosingProductionDataTabContent.tsx b/src/components/pages/closing/ClosingProductionDataTabContent.tsx index aabf48de..0f15d5b9 100644 --- a/src/components/pages/closing/ClosingProductionDataTabContent.tsx +++ b/src/components/pages/closing/ClosingProductionDataTabContent.tsx @@ -197,7 +197,7 @@ const ClosingProductionDataTabContent = ({ value={formatNumber(performance.mor_diff)} unitClassName='hidden' /> - + /> */} Date: Wed, 14 Jan 2026 09:28:47 +0700 Subject: [PATCH 012/169] feat: date filter type --- .../report/DailyMarketingReportContent.tsx | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/DailyMarketingReportContent.tsx b/src/components/pages/report/DailyMarketingReportContent.tsx index 3ddbd6cf..d17df01e 100644 --- a/src/components/pages/report/DailyMarketingReportContent.tsx +++ b/src/components/pages/report/DailyMarketingReportContent.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ChangeEventHandler, useState } from 'react'; +import { ChangeEventHandler, useEffect, useState } from 'react'; import { pdf } from '@react-pdf/renderer'; import toast from 'react-hot-toast'; @@ -28,7 +28,10 @@ import { import { Warehouse } from '@/types/api/master-data/warehouse'; import { Customer } from '@/types/api/master-data/customer'; import { MarketingReportApi } from '@/services/api/report/marketing-report'; -import { MARKETING_TYPE_OPTIONS } from '@/config/constant'; +import { + MARKETING_DATE_FILTER_TYPE_OPTIONS, + MARKETING_TYPE_OPTIONS, +} from '@/config/constant'; import { httpClient } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; import { @@ -150,6 +153,15 @@ const DailyMarketingReportContent = () => { updateFilter('end_date', e.target.value ? e.target.value : ''); }; + const [selectedMarketingDateFilterType, setSelectedMarketingDateFilterType] = + useState(null); + const marketingDateFilterTypeChangeHandler = ( + val: OptionType | OptionType[] | null + ) => { + setSelectedMarketingDateFilterType(val as OptionType); + updateFilter('filter_by', val ? ((val as OptionType).value as string) : ''); + }; + const [selectedMarketingType, setSelectedMarketingType] = useState(null); const marketingTypeChangeHandler = ( @@ -252,6 +264,23 @@ const DailyMarketingReportContent = () => { resetFilter(); }; + useEffect(() => { + if ( + tableFilterState.filter_by === 'realization_date' || + tableFilterState.filter_by === 'so_date' + ) { + setSelectedMarketingDateFilterType({ + label: + tableFilterState.filter_by === 'realization_date' + ? 'Tanggal Realisasi' + : 'Tanggal SO', + value: tableFilterState.filter_by, + }); + } else { + setSelectedMarketingDateFilterType(null); + } + }, [tableFilterState.filter_by]); + return (
@@ -341,6 +370,18 @@ const DailyMarketingReportContent = () => {
+ + Date: Wed, 14 Jan 2026 09:29:15 +0700 Subject: [PATCH 013/169] chore: adjust table columns accessorKey for filter --- .../pages/report/DailyMarketingsTable.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/pages/report/DailyMarketingsTable.tsx b/src/components/pages/report/DailyMarketingsTable.tsx index 94da96f8..2dba0309 100644 --- a/src/components/pages/report/DailyMarketingsTable.tsx +++ b/src/components/pages/report/DailyMarketingsTable.tsx @@ -71,19 +71,22 @@ const DailyMarketingsTable = ({ cell: (props) => `${props.row.original.aging_days} hari`, }, { - accessorKey: 'warehouse.name', + accessorKey: 'warehouse', header: 'Gudang', + cell: ({ row }) => row.original.warehouse.name, }, { - accessorKey: 'customer.name', + accessorKey: 'customer', header: 'Pelanggan', + cell: ({ row }) => row.original.customer.name, }, { accessorKey: 'do_number', header: 'No. DO', + enableSorting: false, }, { - accessorKey: 'sales', + accessorKey: 'sales_person', header: 'Sales/Marketing', cell: (props) => props.row.original.sales.name, }, @@ -97,10 +100,12 @@ const DailyMarketingsTable = ({ { accessorKey: 'marketing_type', header: 'Marketing Type', + enableSorting: false, }, { - accessorKey: 'product.name', + accessorKey: 'product', header: 'Produk', + cell: ({ row }) => row.original.product.name, }, { accessorKey: 'qty', @@ -115,12 +120,12 @@ const DailyMarketingsTable = ({ }, }, { - accessorKey: 'average_weight_kg', + accessorKey: 'average_weight', header: 'Bobot Rata-Rata (Kg)', cell: (props) => formatNumber(props.row.original.average_weight_kg), }, { - accessorKey: 'total_weight_kg', + accessorKey: 'total_weight', header: 'Bobot Total (Kg)', cell: (props) => formatNumber(props.row.original.total_weight_kg), footer: () => { @@ -132,12 +137,12 @@ const DailyMarketingsTable = ({ }, }, { - accessorKey: 'sales_price_per_kg', + accessorKey: 'sales_price', header: 'Harga Jual (Rp)', cell: (props) => formatCurrency(props.row.original.sales_price_per_kg), }, { - accessorKey: 'hpp_price_per_kg', + accessorKey: 'hpp_price', header: 'HPP (Rp)', cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg), footer: () => { @@ -163,6 +168,8 @@ const DailyMarketingsTable = ({ ]; useEffect(() => { + console.log({ sorting }); + if (sorting.length === 1) { onFilterByChange(sorting[0].id); onSortByChange(sorting[0].desc ? 'desc' : 'asc'); From c4b505047cd656456c6bc62bd0245afc0859a20e Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Wed, 14 Jan 2026 09:29:27 +0700 Subject: [PATCH 014/169] chore: add MARKETING_DATE_FILTER_TYPE_OPTIONS constant --- src/config/constant.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/config/constant.ts b/src/config/constant.ts index 364f1824..d3832613 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -457,3 +457,14 @@ export const MARKETING_TYPE_OPTIONS = [ value: 'trading', }, ]; + +export const MARKETING_DATE_FILTER_TYPE_OPTIONS = [ + { + label: 'Tanggal Realisasi', + value: 'realization_date', + }, + { + label: 'Tanggal SO', + value: 'so_date', + }, +]; From adb8d0f69e9f455dba5172277b1e56dcca369c2e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Wed, 14 Jan 2026 10:04:51 +0700 Subject: [PATCH 015/169] feat(FE): Add checkbox multi-select and components prop --- src/components/input/SelectInput.tsx | 23 ++++-- src/components/input/SelectInputCheckbox.tsx | 82 +++++++++++++++++++ .../report/finance/tab/CustomerPaymentTab.tsx | 4 +- 3 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 src/components/input/SelectInputCheckbox.tsx diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index d35e7589..e3dbc011 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -35,6 +35,7 @@ interface SelectInputBaseProps { bottomLabel?: ReactNode; options: T[]; optionComponent?: OptionComponent; + components?: Partial; isDisabled?: boolean; isLoading?: boolean; isClearable?: boolean; @@ -56,9 +57,12 @@ interface SelectInputBaseProps { onInputChange?: (search: string) => void; startAdornment?: ReactNode; menuPortalTarget?: HTMLElement | null; + closeMenuOnSelect?: boolean; + hideSelectedOptions?: boolean; } -interface SelectInputProps extends SelectInputBaseProps { +export interface SelectInputProps + extends SelectInputBaseProps { createables?: boolean; value?: T | T[] | null; onChange?: (val: T | T[] | null) => void; @@ -101,6 +105,7 @@ const SelectInput = (props: SelectInputProps) => { onChange, options, optionComponent, + components: customComponents, isDisabled, isLoading, isClearable, @@ -119,6 +124,8 @@ const SelectInput = (props: SelectInputProps) => { onInputChange, startAdornment, menuPortalTarget, + closeMenuOnSelect, + hideSelectedOptions, } = props; const [internalInputValue, setInternalInputValue] = useState(''); @@ -128,14 +135,18 @@ const SelectInput = (props: SelectInputProps) => { const components = useMemo(() => { const base = isAnimated ? animatedComponents : {}; - const customComponents = { ...base, IndicatorSeparator: () => null }; + const mergedComponents = { ...base, IndicatorSeparator: () => null }; if (startAdornment) { - customComponents.Control = CustomControl; + mergedComponents.Control = CustomControl; } - return customComponents; - }, [isAnimated, startAdornment]); + if (customComponents) { + Object.assign(mergedComponents, customComponents); + } + + return mergedComponents; + }, [isAnimated, startAdornment, customComponents]); const internalInputChangeHandler = (val: string, meta: InputActionMeta) => { if (meta.action === 'input-change') setInternalInputValue(val); @@ -205,6 +216,8 @@ const SelectInput = (props: SelectInputProps) => { isRtl={isRtl} isSearchable={isSearchable} placeholder={placeholder} + closeMenuOnSelect={closeMenuOnSelect} + hideSelectedOptions={hideSelectedOptions} className={cn('w-full', className?.select)} classNames={{ ...(!startAdornment && { diff --git a/src/components/input/SelectInputCheckbox.tsx b/src/components/input/SelectInputCheckbox.tsx new file mode 100644 index 00000000..0827a70a --- /dev/null +++ b/src/components/input/SelectInputCheckbox.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { useMemo } from 'react'; +import { + OptionProps, + GroupBase, + components as ReactSelectComponents, +} from 'react-select'; +import SelectInput, { OptionType, SelectInputProps } from './SelectInput'; +import { cn } from '@/lib/helper'; + +interface SelectInputCheckboxProps + extends Omit< + SelectInputProps, + 'closeMenuOnSelect' | 'hideSelectedOptions' | 'optionComponent' + > { + closeMenuOnSelect?: boolean; + hideSelectedOptions?: boolean; +} + +const CheckboxOption = < + T extends OptionType, + IsMulti extends boolean, + Group extends GroupBase, +>( + props: OptionProps +) => { + const { isSelected, label, innerRef, innerProps, className } = props; + + return ( +
+ null} + className='checkbox checkbox-sm checkbox-primary pointer-events-none' + /> + +
+ ); +}; + +const SelectInputCheckbox = ( + props: SelectInputCheckboxProps +) => { + const { + closeMenuOnSelect = false, + hideSelectedOptions = false, + isMulti = true, + className, + ...restProps + } = props; + + const customComponents = useMemo(() => { + return { + Option: CheckboxOption as typeof ReactSelectComponents.Option, + }; + }, []); + + return ( + + {...restProps} + isMulti={isMulti} + closeMenuOnSelect={closeMenuOnSelect} + hideSelectedOptions={hideSelectedOptions} + className={{ + ...className, + select: cn(className?.select, 'select-checkbox'), + }} + components={customComponents} + /> + ); +}; + +export default SelectInputCheckbox; diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 2b2c09a2..dfadd1ec 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -7,6 +7,7 @@ import SelectInput, { useSelect, OptionType, } from '@/components/input/SelectInput'; +import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import DateInput from '@/components/input/DateInput'; import { CustomerApi } from '@/services/api/master-data'; import { FinanceApi } from '@/services/api/report/finance-report'; @@ -608,10 +609,9 @@ const CustomerPaymentTab = () => {
- { From 8d7adbbd27b4948ad86c842c5fd7f74d873ef723 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Wed, 14 Jan 2026 10:35:51 +0700 Subject: [PATCH 016/169] feat: implement lazy loading in SelectInput --- src/components/input/SelectInput.tsx | 136 ++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 21 deletions(-) diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index d35e7589..74e23bc4 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -9,15 +9,20 @@ import Select, { SingleValue, components as ReactSelectComponents, ControlProps, + MenuListProps, } from 'react-select'; import CreatableSelect from 'react-select/creatable'; import makeAnimated from 'react-select/animated'; import { useDebounce } from 'use-debounce'; import { cn, getByPath } from '@/lib/helper'; -import useSWR from 'swr'; +import useSWRInfinite from 'swr/infinite'; import { httpClientFetcher } from '@/services/http/client'; -import { BaseApiResponse } from '@/types/api/api-general'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { + BaseApiResponse, + ErrorApiResponse, + SuccessApiResponse, +} from '@/types/api/api-general'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; export interface OptionType { value: string | number; @@ -56,6 +61,7 @@ interface SelectInputBaseProps { onInputChange?: (search: string) => void; startAdornment?: ReactNode; menuPortalTarget?: HTMLElement | null; + onMenuScrollToBottom?: ((event: WheelEvent | TouchEvent) => void) | undefined; } interface SelectInputProps extends SelectInputBaseProps { @@ -93,6 +99,29 @@ const CustomControl = < ); }; +const CustomMenuList = < + Option, + IsMulti extends boolean, + Group extends GroupBase
{type !== 'add' && ( From 395270464396925bb38f2af68e5b00a3fb8167e2 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 11:45:51 +0700 Subject: [PATCH 060/169] chore: fix typo in placeholder --- src/components/pages/master-data/kandang/form/KandangForm.tsx | 2 +- src/components/pages/master-data/nonstock/form/NonstockForm.tsx | 2 +- .../pages/master-data/warehouse/form/WarehouseForm.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/pages/master-data/kandang/form/KandangForm.tsx b/src/components/pages/master-data/kandang/form/KandangForm.tsx index acced3c5..22ad91f8 100644 --- a/src/components/pages/master-data/kandang/form/KandangForm.tsx +++ b/src/components/pages/master-data/kandang/form/KandangForm.tsx @@ -215,7 +215,7 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => { required label='Nama' name='name' - placeholder='Masukkan nama lokasi' + placeholder='Masukkan nama kandang' value={formik.values.name} onChange={formik.handleChange} onBlur={formik.handleBlur} diff --git a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx index cd2c361b..4622a6a3 100644 --- a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx +++ b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx @@ -229,7 +229,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { required label='Nama' name='name' - placeholder='Masukkan nama lokasi' + placeholder='Masukkan nama nonstock' value={formik.values.name} onChange={formik.handleChange} onBlur={formik.handleBlur} diff --git a/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx b/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx index cab9f750..a6a53e3f 100644 --- a/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx +++ b/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx @@ -330,7 +330,7 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => { required label='Nama' name='name' - placeholder='Masukkan nama lokasi' + placeholder='Masukkan nama warehouse' value={formik.values.name} onChange={formik.handleChange} onBlur={formik.handleBlur} From 790b59066819aaccf4dbd931913c3da41ae75c8b Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 11:46:10 +0700 Subject: [PATCH 061/169] feat: use real permission for daily checklist --- src/config/route-permission.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts index 9a0c9d2e..165bc8ee 100644 --- a/src/config/route-permission.ts +++ b/src/config/route-permission.ts @@ -5,22 +5,22 @@ export const ROUTE_PERMISSIONS: Record = { '/dashboard/': ['lti.dashboard.list'], // Daily Checklist - // TODO: use real daily checklist permission name - // '/daily-checklist/': ['lti.daily_checklist.list'], - // '/daily-checklist/dashboard/': ['lti.daily_checklist.list'], - // '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'], - // '/daily-checklist/list-daily-checklist/detail/': ['lti.daily_checklist.detail'], - // '/daily-checklist/reports/': ['lti.daily_checklist.reports'], - // '/daily-checklist/master-data/employee/': ['lti.dashboard.master_data.employee'], - // '/daily-checklist/master-data/activity/': ['lti.dashboard.master_data.activity'], - '/daily-checklist/dashboard/': ['lti.dashboard.list'], - '/daily-checklist/daily-checklist/': ['lti.dashboard.list'], - '/daily-checklist/list-daily-checklist/': ['lti.dashboard.list'], - '/daily-checklist/list-daily-checklist/detail/': ['lti.dashboard.list'], - '/daily-checklist/reports/': ['lti.dashboard.list'], - '/daily-checklist/master-data/employee/': ['lti.dashboard.list'], - '/daily-checklist/master-data/activity/': ['lti.dashboard.list'], - '/daily-checklist/master-data/configuration/': ['lti.dashboard.list'], + '/daily-checklist/dashboard/': ['lti.daily_checklist.dashboard.list'], + '/daily-checklist/daily-checklist/': ['lti.daily_checklist.create'], + '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'], + '/daily-checklist/list-daily-checklist/detail/': [ + 'lti.daily_checklist.detail', + ], + '/daily-checklist/reports/': ['lti.daily_checklist.reports'], + '/daily-checklist/master-data/employee/': [ + 'lti.daily_checklist.master_data.employee', + ], + '/daily-checklist/master-data/activity/': [ + 'lti.daily_checklist.master_data.activity', + ], + '/daily-checklist/master-data/configuration/': [ + 'lti.daily_checklist.master_data.configuration', + ], // Production // Production - Project Flock From bf38178969dc6ec919201c0c4da1637894f920dc Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 11:46:19 +0700 Subject: [PATCH 062/169] chore: update product type --- src/types/api/master-data/product.d.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/types/api/master-data/product.d.ts b/src/types/api/master-data/product.d.ts index e82f857e..7fd2c7c1 100644 --- a/src/types/api/master-data/product.d.ts +++ b/src/types/api/master-data/product.d.ts @@ -1,20 +1,20 @@ import { BaseMetadata } from '@/types/api/api-general'; import { Uom } from '@/types/api/master-data/uom'; import { ProductCategory } from '@/types/api/master-data/product-category'; -import { Supplier } from '@/types/api/master-data/supplier'; +import { BaseSupplier, Supplier } from '@/types/api/master-data/supplier'; export type BaseProduct = { id: number; name: string; brand: string; - sku: string; + sku?: string; product_price: number; selling_price?: number; tax?: number; - expiry_period: number; + expiry_period?: number; uom: Uom; product_category: ProductCategory; - suppliers: Supplier[]; + suppliers: (BaseSupplier & { price: number })[]; flags: string[]; }; @@ -23,14 +23,17 @@ export type Product = BaseMetadata & BaseProduct; export type CreateProductPayload = { name: string; brand: string; - sku: string; + sku?: string; uom_id: number; product_category_id: number; product_price: number; - selling_price: number; - tax: number; - expiry_period: number; - supplier_ids: number[]; + selling_price?: number; + tax?: number; + expiry_period?: number; + suppliers: { + supplier_id: number; + price: number; + }[]; flags: string[]; }; From 294c843bd480e3414aa65afe3d3aad3d52935489 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 11:58:38 +0700 Subject: [PATCH 063/169] 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 6a7990e722789afde820c49475a8368a4b3662c7 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 11:59:25 +0700 Subject: [PATCH 064/169] fix: make suppliers optional --- .../product/form/ProductForm.schema.ts | 1 - .../master-data/product/form/ProductForm.tsx | 23 +++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/pages/master-data/product/form/ProductForm.schema.ts b/src/components/pages/master-data/product/form/ProductForm.schema.ts index b85d5d4c..8a1d3de2 100644 --- a/src/components/pages/master-data/product/form/ProductForm.schema.ts +++ b/src/components/pages/master-data/product/form/ProductForm.schema.ts @@ -92,7 +92,6 @@ export const ProductFormSchema: Yup.ObjectSchema = .typeError('Harga wajib diisi!'), }) ) - .min(1, 'Minimal harus ada 1 supplier!') .required('Supplier wajib diisi!'), flags: Yup.array() diff --git a/src/components/pages/master-data/product/form/ProductForm.tsx b/src/components/pages/master-data/product/form/ProductForm.tsx index 7e893f67..8c04d594 100644 --- a/src/components/pages/master-data/product/form/ProductForm.tsx +++ b/src/components/pages/master-data/product/form/ProductForm.tsx @@ -527,6 +527,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => { idx )} isClearable + isDisabled={type === 'detail'} className={{ wrapper: 'min-w-48 w-full', }} @@ -573,16 +574,18 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
-
- -
+ {type !== 'detail' && ( +
+ +
+ )} )}
From dc3b4f1850174873ea1518bef00b770bb9a0bd07 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 12:05:34 +0700 Subject: [PATCH 065/169] 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 066/169] 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 067/169] 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 068/169] 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 069/169] 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'} > 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)} @@ -772,15 +696,11 @@ const HppPerKandangTab = () => { {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/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 070/169] 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 071/169] 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 072/169] 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 781a5ca0d975b0d5969403fddfe7261247f5d971 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 15:14:04 +0700 Subject: [PATCH 073/169] chore: use real permission for daily checklist menu --- src/config/constant.ts | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/config/constant.ts b/src/config/constant.ts index d3832613..b3621c8f 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -10,61 +10,65 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [ text: 'Daily Checklist', link: '/daily-checklist', icon: 'heroicons-outline:clipboard-check', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: [ + 'lti.daily_checklist.dashboard.list', + 'lti.daily_checklist.create', + 'lti.daily_checklist.list', + 'lti.daily_checklist.detail', + 'lti.daily_checklist.reports', + 'lti.daily_checklist.master_data.employee', + 'lti.daily_checklist.master_data.activity', + 'lti.daily_checklist.master_data.configuration', + ], submenu: [ { text: 'Dashboard', link: '/daily-checklist/dashboard', icon: 'lucide:layout-dashboard', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.dashboard.list'], }, { text: 'Daily Checklist', link: '/daily-checklist/daily-checklist', icon: 'lucide:clipboard-check', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.create'], }, { text: 'Daftar Daily Checklist', link: '/daily-checklist/list-daily-checklist', icon: 'lucide:circle-check', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.list'], }, { text: 'Laporan', link: '/daily-checklist/reports', icon: 'lucide:file-text', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.reports'], }, { text: 'Master Data', link: '/daily-checklist/master-data', icon: 'lucide:database', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: [ + 'lti.daily_checklist.master_data.employee', + 'lti.daily_checklist.master_data.activity', + 'lti.daily_checklist.master_data.configuration', + ], submenu: [ { text: 'Employee (ABK)', link: '/daily-checklist/master-data/employee', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.master_data.employee'], }, { text: 'Aktivitas', link: '/daily-checklist/master-data/activity', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.master_data.activity'], }, { text: 'Konfigurasi', link: '/daily-checklist/master-data/configuration', - // TODO: add permission - // permission: ['lti.daily_checklist.list'], + permission: ['lti.daily_checklist.master_data.configuration'], }, ], }, From 8f55ced55a3c89029a77493f128999ec9edba26f Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 15:14:17 +0700 Subject: [PATCH 074/169] feat: add export to pdf functionality --- .../ProductionResultContent.tsx | 102 +++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/src/components/pages/report/production-result/ProductionResultContent.tsx b/src/components/pages/report/production-result/ProductionResultContent.tsx index 7820ff53..28d334e8 100644 --- a/src/components/pages/report/production-result/ProductionResultContent.tsx +++ b/src/components/pages/report/production-result/ProductionResultContent.tsx @@ -21,10 +21,18 @@ import { ProjectFlockApi, ProjectFlockKandangApi, } from '@/services/api/production'; -import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; -import { isResponseError } from '@/lib/api-helper'; +import { + BaseProjectFlockKandang, + ProjectFlockKandang, +} from '@/types/api/production/project-flock-kandang'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import Pagination from '@/components/Pagination'; import { ProductionResultReportApi } from '@/services/api/report/production-result'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { httpClient } from '@/services/http/client'; +import { ProductionResult } from '@/types/api/report/production-result'; +import ProductionResultReportPDF from './ProductionResultReportPDF'; +import { pdf } from '@react-pdf/renderer'; const ProductionResultContent = () => { const [projectFlockKandangs, setProjectFlockKandangs] = useState< @@ -49,6 +57,8 @@ const ProductionResultContent = () => { const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = useState(false); + const [isLoadingExportingToPdf, setIsLoadingExportingToPdf] = useState(false); + const [selectedArea, setSelectedArea] = useState(null); const [selectedLocation, setSelectedLocation] = useState( null @@ -158,6 +168,87 @@ const ProductionResultContent = () => { setIsLoadingExportingToExcel(false); }; + const exportToPdfHandler = async () => { + setIsLoadingExportingToPdf(true); + + try { + let projectFlockKandangsData: BaseProjectFlockKandang[] = []; + + if (selectedProjectFlockKandang) { + const projectFlockKandangResponse = + await ProjectFlockKandangApi.getSingle( + selectedProjectFlockKandang?.value as number + ); + + projectFlockKandangsData = isResponseSuccess( + projectFlockKandangResponse + ) + ? [projectFlockKandangResponse.data] + : []; + } else { + const projectFlockKandangsResponse = + await ProjectFlockKandangApi.getAll({ + area_id: selectedArea?.value, + project_flock_id: selectedProjectFlock?.value, + }); + + projectFlockKandangsData = isResponseSuccess( + projectFlockKandangsResponse + ) + ? projectFlockKandangsResponse.data + : []; + } + + const mappedProductionResults: { + projectFlockKandang: BaseProjectFlockKandang; + productionResult: ProductionResult[] | null; + }[] = await Promise.all( + projectFlockKandangsData.map(async (projectFlockKandang) => { + const getProductionResultPath = `${ProductionResultReportApi.basePath}/${projectFlockKandang.id}?page=1&limit=100`; + const getProductionResultRes = await httpClient< + BaseApiResponse + >(getProductionResultPath); + + return { + projectFlockKandang, + productionResult: isResponseSuccess(getProductionResultRes) + ? getProductionResultRes.data + : null, + }; + }) + ); + + if (mappedProductionResults.length === 0) { + toast.error('Tidak ada data untuk diexport.'); + setIsLoadingExportingToPdf(false); + return; + } + + const openPdf = async () => { + const productionResultPdfBlob = await pdf( + + ).toBlob(); + + const productionResultReportPdfUrl = URL.createObjectURL( + productionResultPdfBlob + ); + window.open(productionResultReportPdfUrl, '_blank'); + }; + + await openPdf(); + } catch (error) { + console.error(error); + toast.error('Gagal melakukan export laporan hasil produksi! Coba lagi.'); + } + // await ProductionResultReportApi.exportProductionResultToPdf( + // projectFlockKandangs + // ); + + setIsLoadingExportingToPdf(false); + }; + const searchHandler = async () => { setProjectFlockKandangs(null); setIsLoadingSearch(true); @@ -355,6 +446,13 @@ const ProductionResultContent = () => { onClick={exportToExcelHandler} className='text-nowrap' /> +
From e15b7e11d3bd7075d76dd2ffe418da6a3b48133e Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 15:14:33 +0700 Subject: [PATCH 075/169] feat: create ProductionResultReportPDF component --- .../ProductionResultReportPDF.tsx | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 src/components/pages/report/production-result/ProductionResultReportPDF.tsx diff --git a/src/components/pages/report/production-result/ProductionResultReportPDF.tsx b/src/components/pages/report/production-result/ProductionResultReportPDF.tsx new file mode 100644 index 00000000..9bc27c4b --- /dev/null +++ b/src/components/pages/report/production-result/ProductionResultReportPDF.tsx @@ -0,0 +1,388 @@ +'use client'; + +import React from 'react'; +import { + Document, + Page, + StyleSheet, + Text, + View, + Image, +} from '@react-pdf/renderer'; + +import { formatDate, formatNumber } from '@/lib/helper'; +import { BaseProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; +import { ProductionResult } from '@/types/api/report/production-result'; + +type MappedProductionResultsItem = { + projectFlockKandang: BaseProjectFlockKandang; + productionResult: ProductionResult[] | null; +}; + +interface ProductionResultReportPDFProps { + mappedProductionResults?: MappedProductionResultsItem[]; +} + +const styles = StyleSheet.create({ + page: { + paddingTop: 24, + paddingBottom: 52, + paddingHorizontal: 16, + }, + + companyInfoHeader: { + width: '100%', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: 8, + }, + companyLogo: { + width: 64, + height: 'auto', + }, + companyInfoHeaderDate: { + paddingTop: 8, + fontSize: 10, + }, + companyName: { + fontSize: 12, + fontWeight: 'bold', + marginBottom: 4, + }, + companyAddress: { + fontSize: 8, + maxWidth: 420, + marginBottom: 10, + }, + doubleDivider: { + width: '100%', + height: 6, + borderTopWidth: 2, + borderTopColor: '#000', + borderBottomWidth: 2, + borderBottomColor: '#000', + }, + + title: { + marginTop: 14, + fontSize: 14, + lineHeight: '150%', + textAlign: 'center', + fontFamily: 'Times-Roman', + fontWeight: 'bold', + }, + + footer: { + width: '100%', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + position: 'absolute', + fontSize: 8, + bottom: 22, + left: 0, + right: 0, + textAlign: 'center', + color: 'grey', + }, + + section: { + marginTop: 12, + borderWidth: 1, + borderColor: '#000', + padding: 8, + }, + + sectionHeader: { + marginBottom: 6, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'baseline', + }, + sectionTitle: { + fontSize: 10, + fontWeight: 'bold', + }, + sectionSubtitle: { + fontSize: 8, + color: '#444', + }, + + // Simple grid table (label/value pairs) + grid: { + width: '100%', + borderWidth: 1, + borderColor: '#000', + }, + gridRow: { + flexDirection: 'row', + borderBottomWidth: 1, + borderBottomColor: '#000', + }, + gridRowLast: { + borderBottomWidth: 0, + }, + gridCellLabel: { + width: '40%', + paddingVertical: 3, + paddingHorizontal: 6, + fontSize: 8, + borderRightWidth: 1, + borderRightColor: '#000', + fontWeight: 'bold', + }, + gridCellValue: { + width: '60%', + paddingVertical: 3, + paddingHorizontal: 6, + fontSize: 8, + textAlign: 'right', + }, + + // Subsection headings + groupTitle: { + marginTop: 8, + marginBottom: 4, + fontSize: 9, + fontWeight: 'bold', + }, + + emptyText: { + fontSize: 8, + color: '#666', + fontStyle: 'italic', + }, +}); + +function safeNum(v: unknown): number { + const n = typeof v === 'number' ? v : Number(v); + return Number.isFinite(n) ? n : 0; +} + +function valueText(v: unknown) { + if (v === null || v === undefined) return '-'; + if (typeof v === 'number') return formatNumber(v); + return String(v); +} + +/** + * Render label/value table for one ProductionResult. + * Uses a compact grid to keep page readable. + */ +function ProductionResultGrid({ pr }: { pr: ProductionResult }) { + const rows: Array<[string, string]> = [ + ['WOA', valueText(pr.woa)], + + // BW + ['BW', valueText(pr.bw)], + ['Std BW', valueText(pr.std_bw)], + ['Uniformity', valueText(pr.uniformity)], + ['Std Uniformity', valueText(pr.std_uniformity)], + + // Dep + ['Dep Kum', valueText(pr.dep_kum)], + ['Dep Std', valueText(pr.dep_std)], + + // Butiran + ['Butiran Utuh', valueText(pr.butiran_utuh)], + ['Butiran Putih', valueText(pr.butiran_putih)], + ['Butiran Retak', valueText(pr.butiran_retak)], + ['Butiran Pecah', valueText(pr.butiran_pecah)], + ['Butiran Jumlah', valueText(pr.butiran_jumlah)], + ['Total Butir', valueText(pr.total_butir)], + + // Kg + ['Kg Utuh', valueText(pr.kg_utuh)], + ['Kg Putih', valueText(pr.kg_putih)], + ['Kg Retak', valueText(pr.kg_retak)], + ['Kg Pecah', valueText(pr.kg_pecah)], + ['Kg Jumlah', valueText(pr.kg_jumlah)], + ['Total Kg', valueText(pr.total_kg)], + + // % + ['% Utuh', valueText(pr.persen_utuh)], + ['% Putih', valueText(pr.persen_putih)], + ['% Retak', valueText(pr.persen_retak)], + ['% Pecah', valueText(pr.persen_pecah)], + + // Produksi + ['HD', valueText(pr.hd)], + ['HD Std', valueText(pr.hd_std)], + ['FI', valueText(pr.fi)], + ['FI Std', valueText(pr.fi_std)], + ['EM', valueText(pr.em)], + ['EM Std', valueText(pr.em_std)], + ['EW', valueText(pr.ew)], + ['EW Std', valueText(pr.ew_std)], + ['FCR', valueText(pr.fcr)], + ['FCR Std', valueText(pr.fcr_std)], + ['HH', valueText(pr.hh)], + ['HH Std', valueText(pr.hh_std)], + ]; + + return ( + + {rows.map(([label, value], idx) => { + const isLast = idx === rows.length - 1; + return ( + + {label} + {value} + + ); + })} + + ); +} + +/** + * If there are multiple ProductionResult entries for a kandang, + * we show them sequentially with a small header per result. + * + * You can later change this to render only the latest WOA, or group by week. + */ +function ProductionResultList({ + productionResults, +}: { + productionResults: ProductionResult[]; +}) { + return ( + + {productionResults.map((pr, idx) => { + const kandangName = + pr.project_flock?.kandang?.name || + pr.project_flock?.kandang?.id?.toString() || + ''; + + // Optional: show a compact subheader + const headerLeft = `Data #${idx + 1}`; + const headerRight = + kandangName && pr.woa !== undefined + ? `${kandangName} • WOA ${safeNum(pr.woa)}` + : pr.woa !== undefined + ? `WOA ${safeNum(pr.woa)}` + : ''; + + return ( + + + {headerLeft} + {headerRight} + + + + + ); + })} + + ); +} + +/** + * ✅ Main PDF Component + */ +const ProductionResultReportPDF = ({ + mappedProductionResults = [], +}: ProductionResultReportPDFProps) => { + return ( + + + {/* Header */} + + + + + {formatDate(Date.now(), 'DD MMMM YYYY')} + + + + + PT LUMBUNG TELUR INDONESIA + + SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel. + Cipedes, Kec. Sukajadi, Kota Bandung 40162 + + + + + + + Laporan Production Result + + {/* Sections per ProjectFlockKandang */} + {mappedProductionResults.length === 0 ? ( + + Tidak ada data. + + ) : ( + mappedProductionResults.map((item, idx) => { + const pfk = item.projectFlockKandang; + + // Try to display meaningful identifiers. + // Adjust these fields based on your real BaseProjectFlockKandang structure. + const kandangName = + pfk?.kandang?.name ?? `Kandang #${pfk?.kandang_id ?? idx + 1}`; + + const projectName = pfk?.project_flock?.name ?? ''; + + const locationName = pfk?.project_flock?.location?.name ?? ''; + + const areaName = pfk?.project_flock?.area?.name ?? ''; + + return ( + 0} // each kandang starts on a new page for clarity + > + + + {projectName + ? `${projectName} • ${kandangName}` + : kandangName} + + + {[areaName, locationName].filter(Boolean).join(' • ')} + + + + {item.productionResult && item.productionResult.length > 0 ? ( + + ) : ( + + Tidak ada production result untuk kandang ini. + + )} + + ); + }) + )} + + {/* Footer */} + + + `${pageNumber} / ${totalPages}` + } + fixed + /> + + + + ); +}; + +export default ProductionResultReportPDF; From 23e8487a9702733fc8651bef9c37cfcbcdf4cb84 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 15:21:16 +0700 Subject: [PATCH 076/169] 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) => { 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) : '-'} @@ -1716,8 +1709,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { 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}%` : '-'} @@ -1824,17 +1807,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { 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}%` : '-'} From d786b7b5ba8795565aedad172554419f4f0c3160 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 15:41:11 +0700 Subject: [PATCH 077/169] 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 078/169] 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 bd64694c7324619a6394fe801edf3f1e1b436e29 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 15:56:30 +0700 Subject: [PATCH 079/169] feat: implement closing sapronak per kandang --- .../pages/closing/ClosingIncomingSapronaksTable.tsx | 6 +++++- .../pages/closing/ClosingOutgoingSapronaksTable.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/pages/closing/ClosingIncomingSapronaksTable.tsx b/src/components/pages/closing/ClosingIncomingSapronaksTable.tsx index 53e45710..eda7e756 100644 --- a/src/components/pages/closing/ClosingIncomingSapronaksTable.tsx +++ b/src/components/pages/closing/ClosingIncomingSapronaksTable.tsx @@ -1,6 +1,7 @@ 'use client'; import { ChangeEventHandler, useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import { ColumnDef, SortingState } from '@tanstack/react-table'; @@ -23,6 +24,9 @@ interface ClosingIncomingSapronaksTableProps { const ClosingIncomingSapronaksTable = ({ projectFlockId, }: ClosingIncomingSapronaksTableProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + const { state: tableFilterState, updateFilter, @@ -43,7 +47,7 @@ const ClosingIncomingSapronaksTable = ({ const { data: incomingSapronaks, isLoading: isLoadingIncomingSapronaks } = useSWR( - `${ClosingApi.basePath}/${projectFlockId}/sapronak${getTableFilterQueryString()}&type=incoming`, + `${ClosingApi.basePath}/${projectFlockId}/sapronak${getTableFilterQueryString()}&type=incoming&kandang_id=${kandangId ? `${kandangId}` : ''}`, ClosingApi.getAllIncomingSapronakFetcher, { keepPreviousData: true, diff --git a/src/components/pages/closing/ClosingOutgoingSapronaksTable.tsx b/src/components/pages/closing/ClosingOutgoingSapronaksTable.tsx index 5662cff1..ac918561 100644 --- a/src/components/pages/closing/ClosingOutgoingSapronaksTable.tsx +++ b/src/components/pages/closing/ClosingOutgoingSapronaksTable.tsx @@ -1,6 +1,7 @@ 'use client'; import { ChangeEventHandler, useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import { ColumnDef, SortingState } from '@tanstack/react-table'; @@ -23,6 +24,9 @@ interface ClosingOutgoingSapronaksTableProps { const ClosingOutgoingSapronaksTable = ({ projectFlockId, }: ClosingOutgoingSapronaksTableProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + const { state: tableFilterState, updateFilter, @@ -43,7 +47,7 @@ const ClosingOutgoingSapronaksTable = ({ const { data: outgoingSapronaks, isLoading: isLoadingOutgoingSapronaks } = useSWR( - `${ClosingApi.basePath}/${projectFlockId}/sapronak${getTableFilterQueryString()}&type=outgoing`, + `${ClosingApi.basePath}/${projectFlockId}/sapronak${getTableFilterQueryString()}&type=outgoing&kandang_id=${kandangId ? `${kandangId}` : ''}`, ClosingApi.getAllOutgoingSapronakFetcher, { keepPreviousData: true, From fce2cfee736efea0828bcca11e62fbf85e6a49a5 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 15:56:47 +0700 Subject: [PATCH 080/169] feat: implement closing production data per kandang --- .../pages/closing/ClosingProductionDataTabContent.tsx | 8 ++++++-- src/services/api/closing.ts | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/pages/closing/ClosingProductionDataTabContent.tsx b/src/components/pages/closing/ClosingProductionDataTabContent.tsx index 0f15d5b9..9295d283 100644 --- a/src/components/pages/closing/ClosingProductionDataTabContent.tsx +++ b/src/components/pages/closing/ClosingProductionDataTabContent.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import { ClosingApi } from '@/services/api/closing'; import { isResponseSuccess } from '@/lib/api-helper'; @@ -12,9 +13,12 @@ interface ClosingProductionDataTabContentProps { const ClosingProductionDataTabContent = ({ projectFlockId, }: ClosingProductionDataTabContentProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + const { data: productionData, isLoading } = useSWR( - `${ClosingApi.basePath}/${projectFlockId}/production-data`, - () => ClosingApi.getProductionData(projectFlockId) + `${ClosingApi.basePath}/${projectFlockId}/production-data?kandang_id=${kandangId ? `${kandangId}` : ''}`, + () => ClosingApi.getProductionData(projectFlockId, Number(kandangId)) ); if (isLoading) { diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index b2ba2b8f..323e09e8 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -91,10 +91,11 @@ export class ClosingApiService extends BaseApiService { } async getProductionData( - id: number + id: number, + kandangId?: number ): Promise | undefined> { try { - const getProductionDataPath = `${this.basePath}/${id}/production-data`; + const getProductionDataPath = `${this.basePath}/${id}/production-data?kandang_id=${kandangId ? `${kandangId}` : ''}`; const getProductionDataRes = await httpClient< BaseApiResponse >(getProductionDataPath); From 438082c94cfb1d51042cfb7bdbddac5a9e3b2f67 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 16:05:05 +0700 Subject: [PATCH 081/169] fix(FE): fixing error message on submit and fixing ui --- .../form/ProductionStandardForm.schema.ts | 34 ++--- .../form/ProductionStandardForm.tsx | 131 ++++++++++++------ src/services/hooks/useFormikErrorList.ts | 3 + 3 files changed, 104 insertions(+), 64 deletions(-) diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts index 13183e71..eb59a9c0 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts @@ -2,34 +2,30 @@ import * as Yup from 'yup'; // Schema for LAYING category (production_standard_details is required) const LayingRepeaterFormSchema = Yup.object({ - week: Yup.number().required('Minggu wajib diisi!'), + week: Yup.number().required('Wajib diisi!'), production_standard_uniformity_details: Yup.object({ - target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'), - max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'), - min_uniformity: Yup.number().required('Minimal uniformitas wajib diisi!'), - feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'), + target_mean_bw: Yup.number().required('Wajib diisi!'), + max_depletion: Yup.number().required('Wajib diisi!'), + min_uniformity: Yup.number().required('Wajib diisi!'), + feed_intake: Yup.number().required('Wajib diisi!'), }), production_standard_details: Yup.object({ - target_hen_day_production: Yup.number().required( - 'Produksi telur per hari wajib diisi!' - ), - target_hen_house_production: Yup.number().required( - 'Produksi telur per kandang wajib diisi!' - ), - target_egg_weight: Yup.number().required('Berat telur wajib diisi!'), - target_egg_mass: Yup.number().required('Massa telur wajib diisi!'), - standard_fcr: Yup.number().required('FCR wajib diisi!'), + target_hen_day_production: Yup.number().required('Wajib diisi!'), + target_hen_house_production: Yup.number().required('Wajib diisi!'), + target_egg_weight: Yup.number().required('Wajib diisi!'), + target_egg_mass: Yup.number().required('Wajib diisi!'), + standard_fcr: Yup.number().required('Wajib diisi!'), }).required(), }); // Schema for GROWING category (production_standard_details is optional) const GrowingRepeaterFormSchema = Yup.object({ - week: Yup.number().required('Minggu wajib diisi!'), + week: Yup.number().required('Wajib diisi!'), production_standard_uniformity_details: Yup.object({ - target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'), - max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'), - min_uniformity: Yup.number().required('Minimal uniformitas wajib diisi!'), - feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'), + target_mean_bw: Yup.number().required('Wajib diisi!'), + max_depletion: Yup.number().required('Wajib diisi!'), + min_uniformity: Yup.number().required('Wajib diisi!'), + feed_intake: Yup.number().required('Wajib diisi!'), }), production_standard_details: Yup.object({ target_hen_day_production: Yup.number().optional(), diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx index 8dfc5f45..4512f474 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx @@ -344,7 +344,7 @@ const ProductionStandardForm = ({ const columns = useMemo[]>(() => { const baseColumns: ColumnDef[] = [ { - header: 'Minggu', + header: 'Week', accessorKey: 'week', enableSorting: false, }, @@ -358,30 +358,40 @@ const ProductionStandardForm = ({ header: 'Hen Day', accessorFn: (row) => row.production_standard_details?.target_hen_day_production, + cell: ({ row }) => + `${row.original.production_standard_details?.target_hen_day_production}%`, enableSorting: false, }, { header: 'Hen House', accessorFn: (row) => row.production_standard_details?.target_hen_house_production, + cell: ({ row }) => + `${row.original.production_standard_details?.target_hen_house_production} pc`, enableSorting: false, }, { header: 'Egg Weight', accessorFn: (row) => row.production_standard_details?.target_egg_weight, + cell: ({ row }) => + `${row.original.production_standard_details?.target_egg_weight} g`, enableSorting: false, }, { header: 'Egg Mass', accessorFn: (row) => row.production_standard_details?.target_egg_mass, + cell: ({ row }) => + `${row.original.production_standard_details?.target_egg_mass} g`, enableSorting: false, }, { header: 'FCR', accessorFn: (row) => row.production_standard_details?.standard_fcr, + cell: ({ row }) => + `${row.original.production_standard_details?.standard_fcr} g`, enableSorting: false, }, ] @@ -393,24 +403,32 @@ const ProductionStandardForm = ({ header: 'Mean BW', accessorFn: (row) => row.production_standard_uniformity_details?.target_mean_bw, + cell: ({ row }) => + `${row.original.production_standard_uniformity_details?.target_mean_bw} g`, enableSorting: false, }, { header: 'Max Depletion', accessorFn: (row) => row.production_standard_uniformity_details?.max_depletion, + cell: ({ row }) => + `${row.original.production_standard_uniformity_details?.max_depletion}%`, enableSorting: false, }, { header: 'Min Uniformity', accessorFn: (row) => row.production_standard_uniformity_details?.min_uniformity, + cell: ({ row }) => + `${row.original.production_standard_uniformity_details?.min_uniformity}%`, enableSorting: false, }, { header: 'Feed Intake', accessorFn: (row) => row.production_standard_uniformity_details?.feed_intake, + cell: ({ row }) => + `${row.original.production_standard_uniformity_details?.feed_intake} g`, enableSorting: false, }, ]; @@ -728,7 +746,52 @@ const ProductionStandardForm = ({ }; // ===== Formik Error List ===== - const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + const { formErrorList, close, handleFormSubmit } = useFormikErrorList( + formik, + { + onBeforeSubmit: (e) => { + e.preventDefault(); + + // For GROWING category, clear production_standard_details errors and set default values + if (formik.values.project_category === 'GROWING') { + // Set default values for production_standard_details + formik.values.details?.forEach((detail) => { + detail.production_standard_details = { + target_hen_day_production: 0, + target_hen_house_production: 0, + target_egg_weight: 0, + target_egg_mass: 0, + standard_fcr: 0, + }; + }); + + // Clear any errors related to production_standard_details + const currentErrors = { ...formik.errors }; + if (currentErrors.details && Array.isArray(currentErrors.details)) { + const cleanedDetails = currentErrors.details + .map((detailError) => { + if (detailError && typeof detailError === 'object') { + const { production_standard_details, ...rest } = detailError; + return Object.keys(rest).length > 0 ? rest : undefined; + } + return detailError; + }) + .filter( + (error): error is Exclude => + error !== undefined + ); + + currentErrors.details = ( + cleanedDetails.length > 0 ? cleanedDetails : undefined + ) as typeof currentErrors.details; + } + formik.setErrors(currentErrors); + } + + return true; + }, + } + ); return ( <> @@ -821,19 +884,20 @@ const ProductionStandardForm = ({ key={`row-${row.index}`} className='sticky bottom-0 bg-base-100 shadow-lg' > - +
} + bottomLabel='Persen (%)' errorMessage={getProductionDetailsError( repeaterFormik.errors .production_standard_details, @@ -894,11 +958,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={ -
- Butir -
- } + bottomLabel='Butir (pc)' errorMessage={getProductionDetailsError( repeaterFormik.errors .production_standard_details, @@ -930,11 +990,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={ -
- gr -
- } + bottomLabel='Gram (g)' errorMessage={getProductionDetailsError( repeaterFormik.errors .production_standard_details, @@ -959,17 +1015,13 @@ const ProductionStandardForm = ({ name='production_standard_details.target_egg_mass' label='Egg Mass' placeholder='1' + bottomLabel='Gram (g)' value={ repeaterFormik.values .production_standard_details?.target_egg_mass } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={ -
- gr -
- } errorMessage={getProductionDetailsError( repeaterFormik.errors .production_standard_details, @@ -1000,11 +1052,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={ -
- gr -
- } + bottomLabel='Gram (g)' errorMessage={getProductionDetailsError( repeaterFormik.errors .production_standard_details, @@ -1038,11 +1086,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={ -
- gr -
- } + bottomLabel='Gram (g)' errorMessage={ repeaterFormik.errors .production_standard_uniformity_details @@ -1072,7 +1116,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={} + bottomLabel='Persen (%)' errorMessage={ repeaterFormik.errors .production_standard_uniformity_details @@ -1102,7 +1146,7 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={} + bottomLabel='Persen (%)' errorMessage={ repeaterFormik.errors .production_standard_uniformity_details @@ -1132,11 +1176,8 @@ const ProductionStandardForm = ({ } onChange={repeaterFormik.handleChange} onBlur={repeaterFormik.handleBlur} - endAdornment={ -
- gr/ekor -
- } + bottomLabel='Gram/Ekor (g)' + endAdornment errorMessage={ repeaterFormik.errors .production_standard_uniformity_details @@ -1162,7 +1203,7 @@ const ProductionStandardForm = ({ type='button' color='error' variant='outline' - className='min-w-24' + className='min-w-xs' onClick={handleCancelEdit} > Batal @@ -1178,7 +1219,7 @@ const ProductionStandardForm = ({
-
-
- 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 7114470c131cc657ab402202d7bdd1b2acc187ab Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 16:11:12 +0700 Subject: [PATCH 083/169] chore: access correct UOM id --- .../pages/master-data/nonstock/form/NonstockForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx index 4622a6a3..83b53a7d 100644 --- a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx +++ b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx @@ -83,7 +83,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { const formikInitialValues = useMemo(() => { return { name: initialValues?.name ?? '', - uomId: initialValues?.uom_id ?? 0, + uomId: initialValues?.uom?.id ?? 0, uom: initialValues?.uom ? { value: initialValues?.uom?.id, @@ -199,6 +199,10 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { // ===== Formik Error List ===== const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + console.log({ + values: formik.values, + }); + return ( <>
From b2ce9c93b72529ccb1e4595ea6f766245bedd985 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 15 Jan 2026 16:13:54 +0700 Subject: [PATCH 084/169] chore: remove unnecessary data --- .../pages/master-data/nonstock/form/NonstockForm.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx index 83b53a7d..bfe88c0e 100644 --- a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx +++ b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx @@ -199,10 +199,6 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { // ===== Formik Error List ===== const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); - console.log({ - values: formik.values, - }); - return ( <>
From 4285e2e26963eefd254ea2c8dbf21c6429e83410 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 16:16:32 +0700 Subject: [PATCH 085/169] 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 086/169] 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 087/169] 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 088/169] 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 089/169] 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 e349b9dfa446b73cf31e9bf7e1a32cb9fbad1ac6 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 16:48:11 +0700 Subject: [PATCH 090/169] fix(FE): implement lazy loading select button on finance module --- src/components/pages/finance/FinanceTable.tsx | 26 +++++++--------- .../pages/finance/add/FormFinanceAdd.tsx | 14 ++++++--- .../FormFinanceAddInitialBalance.tsx | 30 ++++++++++++------- .../add/injection/FormFinanceInjection.tsx | 6 +++- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index 2d43d273..227c3731 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -1,21 +1,17 @@ import { ChangeEventHandler, useMemo, useState } from 'react'; -import { CellContext, Row } from '@tanstack/react-table'; +import { CellContext } from '@tanstack/react-table'; import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import Button from '@/components/Button'; import Card from '@/components/Card'; -import Dropdown from '@/components/dropdown/Dropdown'; import DateInput from '@/components/input/DateInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; -import Menu from '@/components/menu/Menu'; -import MenuItem from '@/components/menu/MenuItem'; import Table from '@/components/Table'; -import Tooltip from '@/components/Tooltip'; import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { Finance } from '@/types/api/finance/finance'; @@ -23,11 +19,10 @@ import { FINANCE_INITIAL_BALANCE_STATUS, FINANCE_INJECTION_STATUS, FINANCE_TRANSACTION_STATUS, - ROWS_OPTIONS, } from '@/config/constant'; import { FinanceApi } from '@/services/api/finance'; import { isResponseSuccess } from '@/lib/api-helper'; -import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data'; +import { BankApi } from '@/services/api/master-data'; import { Bank } from '@/types/api/master-data/bank'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; @@ -219,15 +214,12 @@ const FinanceTable = () => { { label: 'Tanggal Dibuat', value: 'created_at' }, ]; }, []); - const { options: bankOptions, rawData: bankRawData } = useSelect( - BankApi.basePath, - 'id', - 'alias', - '', - { - limit: 'limit', - } - ); + const { + options: bankOptions, + rawData: bankRawData, + setInputValue: bankInputValue, + loadMore: bankLoadMore, + } = useSelect(BankApi.basePath, 'id', 'alias'); // ===== Handler ===== const searchChangeHandler: ChangeEventHandler = (e) => { @@ -501,6 +493,8 @@ const FinanceTable = () => { label='Bank' value={selectedBank} onChange={bankChangeHandler} + onInputChange={bankInputValue} + onMenuScrollToBottom={bankLoadMore} isClearable /> ( formik.values.party_type_option?.value === 'CUSTOMER' ? CustomerApi.basePath : SupplierApi.basePath, 'id', - 'name', - '', - { limit: 'limit' } + 'name' ); const { options: bankOptions, rawData: bankRawData, isLoadingOptions: isLoadingBankOptions, - } = useSelect(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); + setInputValue: setBankInputValue, + loadMore: loadMoreBankOptions, + } = useSelect(BankApi.basePath, 'id', 'name'); // ===== Helper Functions ===== const transformFormValuesToPayload = ( @@ -219,6 +221,8 @@ const FormFinanceAdd = ({ placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis transaksi dahulu'}`} options={partyOptions} value={formik.values.party_id_option} + onInputChange={setPartyInputValue} + onMenuScrollToBottom={loadMorePartyOptions} onChange={(value) => { formik.setFieldValue('party_id_option', value); if (isResponseSuccess(partyRawData) && value) { @@ -304,6 +308,8 @@ const FormFinanceAdd = ({ : [] } value={formik.values.bank_id_option} + onInputChange={setBankInputValue} + onMenuScrollToBottom={loadMoreBankOptions} onChange={(value) => { formik.setFieldValue('bank_id_option', value); }} diff --git a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx index 85b63ac3..7bcdbccf 100644 --- a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx +++ b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx @@ -104,21 +104,25 @@ const FormFinanceAddInitialBalance = ({ }); // ===== Options ===== - const { options: partyOptions, isLoadingOptions: isLoadingPartyOptions } = - useSelect( - formik.values.party_type_option?.value === 'CUSTOMER' - ? CustomerApi.basePath - : SupplierApi.basePath, - 'id', - 'name', - '', - { limit: 'limit' } - ); + const { + options: partyOptions, + isLoadingOptions: isLoadingPartyOptions, + setInputValue: setPartyInputValue, + loadMore: loadMorePartyOptions, + } = useSelect( + formik.values.party_type_option?.value === 'CUSTOMER' + ? CustomerApi.basePath + : SupplierApi.basePath, + 'id', + 'name' + ); const { options: bankOptions, rawData: bankRawData, isLoadingOptions: isLoadingBankOptions, - } = useSelect(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); + setInputValue: setBankInputValue, + loadMore: loadMoreBankOptions, + } = useSelect(BankApi.basePath, 'id', 'name'); // ===== Helper Functions ===== const transformFormValuesToPayload = ( @@ -189,6 +193,8 @@ const FormFinanceAddInitialBalance = ({ placeholder='Pilih jenis pihak' options={FINANCE_PARTY_TYPE_OPTIONS} value={formik.values.party_type_option} + onInputChange={setPartyInputValue} + onMenuScrollToBottom={loadMorePartyOptions} onChange={(value) => { formik.setFieldValue('party_type_option', value); formik.setFieldValue('party_id_option', null); @@ -218,6 +224,8 @@ const FormFinanceAddInitialBalance = ({ placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis pihak dahulu'}`} options={partyOptions} value={formik.values.party_id_option} + onInputChange={setPartyInputValue} + onMenuScrollToBottom={loadMorePartyOptions} onChange={(value) => { formik.setFieldValue('party_id_option', value); }} diff --git a/src/components/pages/finance/add/injection/FormFinanceInjection.tsx b/src/components/pages/finance/add/injection/FormFinanceInjection.tsx index 4ddd282c..22663f75 100644 --- a/src/components/pages/finance/add/injection/FormFinanceInjection.tsx +++ b/src/components/pages/finance/add/injection/FormFinanceInjection.tsx @@ -80,7 +80,9 @@ const FormFinanceInjection = ({ options: bankOptions, rawData: bankRawData, isLoadingOptions: isLoadingBankOptions, - } = useSelect(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); + setInputValue: setBankInputValue, + loadMore: loadMoreBankOptions, + } = useSelect(BankApi.basePath, 'id', 'name'); // ===== Helper Functions ===== const transformFormValuesToPayload = ( @@ -162,6 +164,8 @@ const FormFinanceInjection = ({ : [] } value={formik.values.bank_id_option} + onInputChange={setBankInputValue} + onMenuScrollToBottom={loadMoreBankOptions} onChange={(value) => { formik.setFieldValue('bank_id_option', value); }} From 73100aa1ceeca1a2a54a7825bffa3fe3fde7bd93 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 16:53:39 +0700 Subject: [PATCH 091/169] fix(FE): implement lazy loading select button on project flock index --- .../project-flock/ProjectFlockTable.tsx | 83 +++++++------------ 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 5ca0e789..6b935812 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -5,7 +5,10 @@ import Button from '@/components/Button'; import FloatingActionsButton from '@/components/FloatingActionsButton'; import CheckboxInput from '@/components/input/CheckboxInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; @@ -59,9 +62,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const selectedRowIds = Object.keys(rowSelection) .filter((id) => rowSelection[id]) .map((id) => parseInt(id)); - const [locationSelectInputValue, setLocationSelectInputValue] = useState(''); - const [areaSelectInputValue, setAreaSelectInputValue] = useState(''); - const [kandangSelectInputValue, setKandangSelectInputValue] = useState(''); const [selectedArea, setSelectedArea] = useState(null); const [selectedLocation, setSelectedLocation] = useState( null @@ -90,55 +90,25 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { { revalidateOnMount: true } ); - const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({ - search: areaSelectInputValue, - limit: '100', - }).toString()}`; - const { data: areas, isLoading: isLoadingAreas } = useSWR( - areaUrl, - AreaApi.getAllFetcher - ); - - const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ - search: locationSelectInputValue, - area_id: selectedArea != null ? selectedArea.value.toString() : '', - limit: '100', - }).toString()}`; - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationUrl, - LocationApi.getAllFetcher - ); - - const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ - search: kandangSelectInputValue, - location_id: - selectedLocation != null ? selectedLocation.value.toString() : '', - limit: '100', - }).toString()}`; - const { data: kandangs, isLoading: isLoadingKandang } = useSWR( - kandangUrl, - KandangApi.getAllFetcher - ); - - // ===== Data to Options Mapping ====== - const optionsArea = isResponseSuccess(areas) - ? areas?.data.map((area) => ({ - value: area.id, - label: area.name, - })) - : []; - const optionsKandang = isResponseSuccess(kandangs) - ? kandangs?.data.map((kandang) => ({ - value: kandang.id, - label: kandang.name, - })) - : []; - const optionsLocation = isResponseSuccess(locations) - ? locations?.data.map((location) => ({ - value: location.id, - label: location.name, - })) - : []; + // ===== Fetch Data Select ===== + const { + options: optionsArea, + isLoadingOptions: isLoadingArea, + setInputValue: setAreaSelectInputValue, + loadMore: loadMoreArea, + } = useSelect(AreaApi.basePath, 'id', 'name'); + const { + options: optionsLocation, + isLoadingOptions: isLoadingLocation, + setInputValue: setLocationSelectInputValue, + loadMore: loadMoreLocation, + } = useSelect(LocationApi.basePath, 'id', 'name'); + const { + options: optionsKandang, + isLoadingOptions: isLoadingKandang, + setInputValue: setKandangSelectInputValue, + loadMore: loadMoreKandang, + } = useSelect(KandangApi.basePath, 'id', 'name'); // ====== HANDLER ====== const confirmationModalDeleteClickHandler = async () => { @@ -385,7 +355,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { { setSelectedArea(val as OptionType); @@ -395,12 +365,13 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); }} onInputChange={setAreaSelectInputValue} + onMenuScrollToBottom={loadMoreArea} isClearable /> { setSelectedLocation(val as OptionType); @@ -410,6 +381,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); }} onInputChange={setLocationSelectInputValue} + onMenuScrollToBottom={loadMoreLocation} isClearable /> void }) => { ); }} onInputChange={setKandangSelectInputValue} + onMenuScrollToBottom={loadMoreKandang} isClearable /> Date: Thu, 15 Jan 2026 16:55:16 +0700 Subject: [PATCH 092/169] chore: implement delete error message toast --- .../pages/master-data/area/AreasTable.tsx | 11 +++++++++-- .../pages/master-data/bank/BanksTable.tsx | 11 +++++++++-- .../pages/master-data/customer/CustomersTable.tsx | 13 +++++++++++-- .../pages/master-data/fcr/FcrsTable.tsx | 11 +++++++++-- .../pages/master-data/flock/FlocksTable.tsx | 11 +++++++++-- .../pages/master-data/kandang/KandangsTable.tsx | 13 +++++++++++-- .../pages/master-data/location/LocationsTable.tsx | 13 +++++++++++-- .../pages/master-data/nonstock/NonstocksTable.tsx | 13 +++++++++++-- .../product-category/ProductCategoryTable.tsx | 13 +++++++++++-- .../pages/master-data/product/ProductTable.tsx | 15 +++++++++++++-- .../ProductionStandardTable.tsx | 11 +++++++++-- .../pages/master-data/supplier/SupplierTable.tsx | 13 +++++++++++-- .../pages/master-data/uom/UomsTable.tsx | 11 +++++++++-- .../master-data/warehouse/WarehousesTable.tsx | 13 +++++++++++-- 14 files changed, 144 insertions(+), 28 deletions(-) diff --git a/src/components/pages/master-data/area/AreasTable.tsx b/src/components/pages/master-data/area/AreasTable.tsx index 45c4fdff..d92c7840 100644 --- a/src/components/pages/master-data/area/AreasTable.tsx +++ b/src/components/pages/master-data/area/AreasTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Area } from '@/types/api/master-data/area'; import { AreaApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -164,7 +164,14 @@ const AreasTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await AreaApi.delete(selectedArea?.id as number); + const deleteResponse = await AreaApi.delete(selectedArea?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshAreas(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/bank/BanksTable.tsx b/src/components/pages/master-data/bank/BanksTable.tsx index f28f4bd0..c5a564fe 100644 --- a/src/components/pages/master-data/bank/BanksTable.tsx +++ b/src/components/pages/master-data/bank/BanksTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Bank } from '@/types/api/master-data/bank'; import { BankApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -177,7 +177,14 @@ const BanksTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await BankApi.delete(selectedBank?.id as number); + const deleteResponse = await BankApi.delete(selectedBank?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshBanks(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/customer/CustomersTable.tsx b/src/components/pages/master-data/customer/CustomersTable.tsx index 3e442620..e605d9f7 100644 --- a/src/components/pages/master-data/customer/CustomersTable.tsx +++ b/src/components/pages/master-data/customer/CustomersTable.tsx @@ -11,7 +11,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RequirePermission from '@/components/helper/RequirePermission'; import { ROWS_OPTIONS } from '@/config/constant'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; import { CustomerApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -186,7 +186,16 @@ const CustomersTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await CustomerApi.delete(selectedCustomer?.id as number); + const deleteResponse = await CustomerApi.delete( + selectedCustomer?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshCustomers(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/fcr/FcrsTable.tsx b/src/components/pages/master-data/fcr/FcrsTable.tsx index 2d65a406..2eb8d8da 100644 --- a/src/components/pages/master-data/fcr/FcrsTable.tsx +++ b/src/components/pages/master-data/fcr/FcrsTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Fcr } from '@/types/api/master-data/fcr'; import { FcrApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -164,7 +164,14 @@ const FcrsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await FcrApi.delete(selectedFcr?.id as number); + const deleteResponse = await FcrApi.delete(selectedFcr?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshFcrs(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx index ce8f701a..baeaa69e 100644 --- a/src/components/pages/master-data/flock/FlocksTable.tsx +++ b/src/components/pages/master-data/flock/FlocksTable.tsx @@ -19,7 +19,7 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput'; import { ROWS_OPTIONS } from '@/config/constant'; import Table from '@/components/Table'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; const RowsOptions = ({ @@ -182,7 +182,14 @@ const FlockTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await FlockApi.delete(selectedFlock?.id as number); + const deleteResponse = await FlockApi.delete(selectedFlock?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshFlocks(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/kandang/KandangsTable.tsx b/src/components/pages/master-data/kandang/KandangsTable.tsx index 1bd7badb..7d79d456 100644 --- a/src/components/pages/master-data/kandang/KandangsTable.tsx +++ b/src/components/pages/master-data/kandang/KandangsTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Kandang } from '@/types/api/master-data/kandang'; import { KandangApi } from '@/services/api/master-data'; import { cn, formatNumber } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -199,7 +199,16 @@ const KandangsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await KandangApi.delete(selectedKandang?.id as number); + const deleteResponse = await KandangApi.delete( + selectedKandang?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshKandangs(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/location/LocationsTable.tsx b/src/components/pages/master-data/location/LocationsTable.tsx index 10fe46c9..a35ffd09 100644 --- a/src/components/pages/master-data/location/LocationsTable.tsx +++ b/src/components/pages/master-data/location/LocationsTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Location } from '@/types/api/master-data/location'; import { LocationApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -186,7 +186,16 @@ const LocationsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await LocationApi.delete(selectedLocation?.id as number); + const deleteResponse = await LocationApi.delete( + selectedLocation?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshLocations(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/nonstock/NonstocksTable.tsx b/src/components/pages/master-data/nonstock/NonstocksTable.tsx index 7066c19a..6aeb3f99 100644 --- a/src/components/pages/master-data/nonstock/NonstocksTable.tsx +++ b/src/components/pages/master-data/nonstock/NonstocksTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Nonstock } from '@/types/api/master-data/nonstock'; import { NonstockApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -198,7 +198,16 @@ const NonstocksTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await NonstockApi.delete(selectedNonstock?.id as number); + const deleteResponse = await NonstockApi.delete( + selectedNonstock?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshNonstocks(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/product-category/ProductCategoryTable.tsx b/src/components/pages/master-data/product-category/ProductCategoryTable.tsx index a9b98bcb..e25dfd56 100644 --- a/src/components/pages/master-data/product-category/ProductCategoryTable.tsx +++ b/src/components/pages/master-data/product-category/ProductCategoryTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { ProductCategory } from '@/types/api/master-data/product-category'; import { ProductCategoryApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -170,7 +170,16 @@ const ProductCategoryTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await ProductCategoryApi.delete(selectedProductCategory?.id as number); + const deleteResponse = await ProductCategoryApi.delete( + selectedProductCategory?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshProductCategories(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/product/ProductTable.tsx b/src/components/pages/master-data/product/ProductTable.tsx index 957d0551..74137a14 100644 --- a/src/components/pages/master-data/product/ProductTable.tsx +++ b/src/components/pages/master-data/product/ProductTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Product } from '@/types/api/master-data/product'; import { ProductApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -230,8 +230,19 @@ const ProductsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await ProductApi.delete(selectedProduct?.id as number); + + const deleteResponse = await ProductApi.delete( + selectedProduct?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshProducts(); + deleteModal.closeModal(); toast.success('Successfully delete Product!'); setIsDeleteLoading(false); diff --git a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx index afa41295..b56e31bd 100644 --- a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx +++ b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx @@ -7,7 +7,7 @@ import { ProductionStandard } from '@/types/api/master-data/production-standard' import { Icon } from '@iconify/react'; import useSWR from 'swr'; import { ProductionStandardApi } from '@/services/api/master-data'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import { CellContext } from '@tanstack/react-table'; import { useModal } from '@/components/Modal'; @@ -94,9 +94,16 @@ const ProductionStandardTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await ProductionStandardApi.delete( + const deleteResponse = await ProductionStandardApi.delete( selectedProductionStandard?.id as number ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshProductionStandards(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/supplier/SupplierTable.tsx b/src/components/pages/master-data/supplier/SupplierTable.tsx index 3e10c9c8..2620c9e6 100644 --- a/src/components/pages/master-data/supplier/SupplierTable.tsx +++ b/src/components/pages/master-data/supplier/SupplierTable.tsx @@ -11,7 +11,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RequirePermission from '@/components/helper/RequirePermission'; import { ROWS_OPTIONS } from '@/config/constant'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; import { SupplierApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -205,7 +205,16 @@ const SuppliersTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await SupplierApi.delete(selectedSupplier?.id as number); + const deleteResponse = await SupplierApi.delete( + selectedSupplier?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshSuppliers(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/uom/UomsTable.tsx b/src/components/pages/master-data/uom/UomsTable.tsx index 851647b9..51e95661 100644 --- a/src/components/pages/master-data/uom/UomsTable.tsx +++ b/src/components/pages/master-data/uom/UomsTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Uom } from '@/types/api/master-data/uom'; import { UomApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -164,7 +164,14 @@ const UomsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await UomApi.delete(selectedUom?.id as number); + const deleteResponse = await UomApi.delete(selectedUom?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshUoms(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/warehouse/WarehousesTable.tsx b/src/components/pages/master-data/warehouse/WarehousesTable.tsx index fe694322..62c39574 100644 --- a/src/components/pages/master-data/warehouse/WarehousesTable.tsx +++ b/src/components/pages/master-data/warehouse/WarehousesTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Warehouse } from '@/types/api/master-data/warehouse'; import { WarehouseApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -220,7 +220,16 @@ const WarehousesTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await WarehouseApi.delete(selectedWarehouse?.id as number); + const deleteResponse = await WarehouseApi.delete( + selectedWarehouse?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshWarehouses(); deleteModal.closeModal(); From a1bbe4e2d70485ecf100a7b3d7967e748a01f74e Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 17:03:25 +0700 Subject: [PATCH 093/169] fix(FE): implement lazy loading select button on marketing index --- src/components/pages/marketing/MarketingTable.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index 1c37dbbb..467c2e00 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -184,12 +184,16 @@ const MarketingTable = () => { const { options: productsOptions, isLoadingOptions: isLoadingProductsOptions, + setInputValue: setProductsInputValue, + loadMore: loadMoreProducts, } = useSelect(ProductApi.basePath, 'id', 'name', '', { limit: 'limit', }); const { options: customersOptions, isLoadingOptions: isLoadingCustomersOptions, + setInputValue: setCustomersInputValue, + loadMore: loadMoreCustomers, } = useSelect(CustomerApi.basePath, 'id', 'name', '', { limit: 'limit', }); @@ -400,6 +404,8 @@ const MarketingTable = () => { .join(',') || '' ) } + onInputChange={setProductsInputValue} + onMenuScrollToBottom={loadMoreProducts} isMulti /> {/* select status */} @@ -444,6 +450,8 @@ const MarketingTable = () => { (value as OptionType)?.value.toString() || '' ) } + onInputChange={setCustomersInputValue} + onMenuScrollToBottom={loadMoreCustomers} />
From 8fe51c976bd4e4372271dcc7a5fed9ae67b7d027 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 15 Jan 2026 17:29:09 +0700 Subject: [PATCH 094/169] 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 095/169] 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 096/169] 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 097/169] 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 098/169] 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 * - - Nomor Kendaraan - * - - - Vendor Ekspedisi - * - + Nomor Kendaraan + Vendor Ekspedisi Jumlah Diterima * - - Transport/Item - * - + Transport/Item @@ -538,7 +532,6 @@ const PurchaseOrderAcceptApprovalForm = ({ @@ -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 099/169] 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 100/169] 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( From 8c6a87c011c2747af7818eee1f34ce9393e14b2a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 20:08:21 +0700 Subject: [PATCH 101/169] feat(FE): adding closing finance per kandang --- .../pages/closing/ClosingFinanceTable.tsx | 419 +++++------------- src/types/api/closing.d.ts | 79 ++-- 2 files changed, 139 insertions(+), 359 deletions(-) diff --git a/src/components/pages/closing/ClosingFinanceTable.tsx b/src/components/pages/closing/ClosingFinanceTable.tsx index 010bfc2f..0f574c75 100644 --- a/src/components/pages/closing/ClosingFinanceTable.tsx +++ b/src/components/pages/closing/ClosingFinanceTable.tsx @@ -3,54 +3,11 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import { isResponseSuccess } from '@/lib/api-helper'; import { formatCurrency, formatTitleCase } from '@/lib/helper'; import { ClosingApi } from '@/services/api/closing'; -import { - DataSummarySubTotal, - HppPurchaseData, - ProfitLossDataAmount, -} from '@/types/api/closing'; +import { HppItem, ProfitLossItem } from '@/types/api/closing'; import { useSearchParams } from 'next/navigation'; +import { useMemo } from 'react'; import useSWR from 'swr'; -type HppTableRow = - | (HppPurchaseData & { - group_name: string; - group_index: number; - isGroupHeader?: boolean; - }) - | { - group_name: string; - group_index: number; - isGroupHeader: true; - type?: never; - budgeting?: never; - realization?: never; - } - | { - type: string; - group_name: string; - group_index: number; - isGroupHeader: false; - budgeting?: { rp_per_bird: number; rp_per_kg: number; amount: number }; - realization?: { rp_per_bird: number; rp_per_kg: number; amount: number }; - }; - -type ProfitLossTableRow = - | (DataSummarySubTotal & { - type: string; - group_name: string; - group_index: number; - isGroupHeader?: boolean; - }) - | { - group_name: string; - group_index: number; - isGroupHeader: true; - type?: never; - rp_per_bird?: never; - rp_per_kg?: never; - amount?: never; - }; - const ClosingFinanceTable = ({ projectFlockId, }: { @@ -68,167 +25,60 @@ const ClosingFinanceTable = ({ ) ); - const staticHppRows: Array<{ - group_name: string; - type: string; - group_index: number; - }> = [ - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian PAKAN', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian STARTER', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian DOC', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian PULLET', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian LAYER', - group_index: 0, - }, - { - group_name: 'HPP dan Bahan Baku', - type: 'Pengeluaran Overhead', - group_index: 1, - }, - { - group_name: 'HPP dan Bahan Baku', - type: 'Beban Ekspedisi', - group_index: 1, - }, - ]; + const hppTableData: HppItem[] = useMemo(() => { + if (isResponseSuccess(finance)) { + const customItems = { + label: 'HPP dan Pengeluaran', + code: 'custom_row', + } as HppItem; + const purchases = finance.data.hpp.items.filter( + (item) => item.category === 'purchase' + ); + const totalBudgeting = { + label: 'HPP dan Bahan Baku', + code: 'custom_row', + } as HppItem; + const overheads = finance.data.hpp.items.filter( + (item) => item.category === 'overhead' + ); + return [customItems, ...purchases, totalBudgeting, ...overheads]; + } + return []; + }, []); - const hppTableData: HppTableRow[] = [ - { - group_name: 'HPP dan Pengeluaran', - group_index: 0, - isGroupHeader: true as const, - }, - ...staticHppRows - .filter((row) => row.group_index === 0) - .map((staticRow) => { - const apiData = isResponseSuccess(finance) - ? finance.data.hpp_purchases.hpp - .find((g) => g.group_name === staticRow.group_name) - ?.data.find((d) => d.type === staticRow.type) - : null; - - return { - group_name: staticRow.group_name, - group_index: staticRow.group_index, - type: staticRow.type, - budgeting: apiData?.budgeting || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - realization: apiData?.realization || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - isGroupHeader: false as const, - }; - }), - { - group_name: 'HPP dan Bahan Baku', - group_index: 1, - isGroupHeader: true as const, - }, - ...staticHppRows - .filter((row) => row.group_index === 1) - .map((staticRow) => { - const apiData = isResponseSuccess(finance) - ? finance.data.hpp_purchases.hpp - .find((g) => g.group_name === staticRow.group_name) - ?.data.find((d) => d.type === staticRow.type) - : null; - - return { - group_name: staticRow.group_name, - group_index: staticRow.group_index, - type: staticRow.type, - budgeting: apiData?.budgeting || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - realization: apiData?.realization || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - isGroupHeader: false as const, - }; - }), - { - group_name: 'HPP', - group_index: 2, - isGroupHeader: true as const, - }, - ]; - - const profitLossTableData: ProfitLossTableRow[] = isResponseSuccess(finance) - ? [ - // Pembelian group - ...finance.data.profit_loss.data.pembelian.map((item) => ({ - label: 'Pembelian', - group_name: 'Pembelian', - group_index: 1, - type: item.type, - rp_per_bird: item.rp_per_bird, - rp_per_kg: item.rp_per_kg, - amount: item.amount, - isGroupHeader: false as const, - })), - { - label: finance.data.profit_loss.data.summary.gross_profit.label, - group_name: 'Penjualan', - group_index: 0, - isGroupHeader: true as const, - type: finance.data.profit_loss.data.summary.gross_profit.label, - rp_per_bird: - finance.data.profit_loss.data.summary.gross_profit.rp_per_bird, - rp_per_kg: - finance.data.profit_loss.data.summary.gross_profit.rp_per_kg, - amount: finance.data.profit_loss.data.summary.gross_profit.amount, - }, - // Penjualan group - ...finance.data.profit_loss.data.penjualan.map((item) => ({ - label: 'Penjualan', - group_name: 'Penjualan', - group_index: 0, - type: item.type, - rp_per_bird: item.rp_per_bird, - rp_per_kg: item.rp_per_kg, - amount: item.amount, - isGroupHeader: false as const, - })), - { - label: finance.data.profit_loss.data.summary.sub_total.label, - group_name: 'Pembelian', - group_index: 1, - isGroupHeader: true as const, - type: finance.data.profit_loss.data.summary.sub_total.label, - rp_per_bird: - finance.data.profit_loss.data.summary.sub_total.rp_per_bird, - rp_per_kg: finance.data.profit_loss.data.summary.sub_total.rp_per_kg, - amount: finance.data.profit_loss.data.summary.sub_total.amount, - }, - ] - : []; + const profitLossTableData: ProfitLossItem[] = useMemo(() => { + if (isResponseSuccess(finance)) { + const incomes = finance.data.profit_loss.items.filter( + (item) => item.type === 'income' + ); + const purchases = finance.data.profit_loss.items.filter( + (item) => item.type === 'purchase' + ); + const overheads = finance.data.profit_loss.items.filter( + (item) => item.type === 'overhead' + ); + const grossProfit = { + label: 'LABA RUGI BRUTO', + code: 'custom_row', + type: 'gross_profit', + rp_per_bird: + finance.data.profit_loss.summary.gross_profit.rp_per_bird ?? 0, + rp_per_kg: finance.data.profit_loss.summary.gross_profit.rp_per_kg ?? 0, + amount: finance.data.profit_loss.summary.gross_profit.amount ?? 0, + } as ProfitLossItem; + const subtotal = { + label: 'Subtotal', + code: 'custom_row', + type: 'subtotal', + rp_per_bird: + finance.data.profit_loss.summary.sub_total.rp_per_bird ?? 0, + rp_per_kg: finance.data.profit_loss.summary.sub_total.rp_per_kg ?? 0, + amount: finance.data.profit_loss.summary.sub_total.amount ?? 0, + } as ProfitLossItem; + return [...incomes, ...purchases, grossProfit, ...overheads, subtotal]; + } + return []; + }, []); return (
@@ -241,47 +91,30 @@ const ClosingFinanceTable = ({ >
-
- {isResponseSuccess(finance) - ? formatTitleCase( - finance.data.profit_loss.data.summary.gross_profit - .label || '-' - ) - : 'Laba Rugi Brutto'} -
+
Laba Rugi Brutto
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.gross_profit.amount + finance.data.profit_loss.summary.gross_profit.amount ) : '-'}
-
- {isResponseSuccess(finance) - ? formatTitleCase( - finance.data.profit_loss.data.summary.net_profit.label || - '-' - ) - : 'Laba Rugi Netto'} -
+
Laba Rugi Netto
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit.amount + finance.data.profit_loss.summary.net_profit.amount ) : '-'}
+ {JSON.stringify(finance)}
- + data={hppTableData} isLoading={isLoading} columns={[ @@ -297,10 +130,10 @@ const ClosingFinanceTable = ({ header: 'No.', enableSorting: false, accessorFn: (item, index) => { - if (item.isGroupHeader) return '-'; + if (item.code === 'custom_row') return '-'; const dataRowsBefore = hppTableData .slice(0, index) - .filter((row) => !row.isGroupHeader).length; + .filter((row) => row.code !== 'custom_row').length; return dataRowsBefore + 1; }, footer: (props) => { @@ -310,7 +143,7 @@ const ClosingFinanceTable = ({ { header: 'Jenis', enableSorting: false, - accessorFn: (item) => formatTitleCase(item.type || '-'), + accessorFn: (item) => formatTitleCase(item.label || '-'), }, { header: 'Budgeting', @@ -326,7 +159,7 @@ const ClosingFinanceTable = ({ return props.column.id === 'budgeting_rp_per_bird' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp?.budgeting + finance.data.hpp.summary?.budgeting ?.rp_per_bird || 0 ) : '-'; @@ -342,8 +175,8 @@ const ClosingFinanceTable = ({ return props.column.id === 'budgeting_rp_per_kg' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp?.budgeting - ?.rp_per_kg || 0 + finance.data.hpp.summary?.budgeting?.rp_per_kg || + 0 ) : '-'; }, @@ -358,8 +191,7 @@ const ClosingFinanceTable = ({ return props.column.id === 'budgeting_amount' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp?.budgeting - ?.amount || 0 + finance.data.hpp.summary?.budgeting?.amount || 0 ) : '-'; }, @@ -380,8 +212,8 @@ const ClosingFinanceTable = ({ return props.column.id === 'realization_rp_per_bird' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp - ?.realization?.rp_per_bird || 0 + finance.data.hpp.summary?.realization + ?.rp_per_bird || 0 ) : '-'; }, @@ -396,8 +228,8 @@ const ClosingFinanceTable = ({ return props.column.id === 'realization_rp_per_kg' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp - ?.realization?.rp_per_kg || 0 + finance.data.hpp.summary?.realization + ?.rp_per_kg || 0 ) : '-'; }, @@ -412,8 +244,7 @@ const ClosingFinanceTable = ({ return props.column.id === 'realization_amount' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp - ?.realization?.amount || 0 + finance.data.hpp.summary?.realization?.amount || 0 ) : '-'; }, @@ -423,7 +254,7 @@ const ClosingFinanceTable = ({ ]} renderCustomRow={(row) => { const rowData = row.original; - if (rowData.isGroupHeader) { + if (rowData.code === 'custom_row') { return (
- {formatTitleCase(rowData.group_name ?? '-')} + {formatTitleCase(rowData.label ?? '-')}
@@ -450,11 +281,7 @@ const ClosingFinanceTable = ({
- + data={profitLossTableData} isLoading={isLoading} columns={[ { header: 'Jenis', enableSorting: false, - accessorFn: (item) => item.type, + accessorFn: (item) => item.label, cell: (item) => (
- {formatTitleCase(item.row.original.type || '-')} + {formatTitleCase(item.row.original.label || '-')}
), - footer: (item) => ( -
- {isResponseSuccess(finance) - ? formatTitleCase( - finance.data.profit_loss.data.summary.net_profit - .label || '-' - ) - : '-'} -
+ footer: () => ( +
LABA RUGI NETTO
), }, { header: 'Rp/Ekor', enableSorting: false, accessorFn: (item) => formatCurrency(item.rp_per_bird || 0), - footer: (item) => ( + footer: () => (
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit + finance.data.profit_loss.summary.net_profit .rp_per_bird || 0 ) : formatCurrency(0)} @@ -505,11 +325,11 @@ const ClosingFinanceTable = ({ header: 'Rp/Kg', enableSorting: false, accessorFn: (item) => formatCurrency(item.rp_per_kg || 0), - footer: (item) => ( + footer: () => (
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit + finance.data.profit_loss.summary.net_profit .rp_per_kg || 0 ) : formatCurrency(0)} @@ -520,11 +340,11 @@ const ClosingFinanceTable = ({ header: 'Jumlah (Rp)', enableSorting: false, accessorFn: (item) => formatCurrency(item.amount || 0), - footer: (item) => ( + footer: () => (
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit + finance.data.profit_loss.summary.net_profit .amount || 0 ) : formatCurrency(0)} @@ -534,55 +354,30 @@ const ClosingFinanceTable = ({ ]} renderCustomRow={(row) => { const rowData = row.original; - if (rowData.isGroupHeader) { - if (rowData.amount) { - return ( - - -
- {formatTitleCase(rowData.label ?? '-')} -
- - -
- {formatCurrency(rowData.rp_per_bird ?? 0)} -
- - -
- {formatCurrency(rowData.rp_per_kg ?? 0)} -
- - -
- {formatCurrency(rowData.amount ?? 0)} -
- - - ); - } + if (rowData.code === 'custom_row') { return ( - + +
+ {formatTitleCase(rowData.label ?? '-')} +
+ +
- {formatTitleCase(rowData.group_name ?? '-')} + {formatCurrency(rowData.rp_per_bird ?? 0)} +
+ + +
+ {formatCurrency(rowData.rp_per_kg ?? 0)} +
+ + +
+ {formatCurrency(rowData.amount ?? 0)}
diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 56406ada..ff35fd28 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -219,64 +219,30 @@ export type ClosingSales = BaseMetadata & BaseClosingSales; // ====== FINANCE ====== export interface ClosingFinance { - project_flock_id: number; - period: number; - project_type: string; - volume_base: ClosingFinanceVolumeBase; - hpp_purchases: ClosingFinanceHppPurchases; + hpp: ClosingFinanceHpp; profit_loss: ClosingFinanceProfitLoss; } -export interface ClosingFinanceProfitLoss { - title: string; - data: ProfitLossData; +export interface ClosingFinanceHpp { + items: HppItem[]; + summary: HppSummary; } -export interface ClosingFinanceHppPurchases { - title: string; - hpp: GroupHppPurchase[]; - summary_hpp: HppPurchasesSummary; -} - -export interface ClosingFinanceVolumeBase { - total_birds: number; - total_weight_kg: number; -} - -export interface ProfitLossData { - penjualan: ProfitLossDataAmount[]; - pembelian: ProfitLossDataAmount[]; - summary: ProfitLossDataSummary; -} - -export interface GroupHppPurchase { - group_name: string; - data: HppPurchaseData[]; -} - -export interface ProfitLossDataSummary { - gross_profit: DataSummarySubTotal; - sub_total: DataSummarySubTotal; - net_profit: DataSummarySubTotal; -} - -export interface ProfitLossDataAmount { - type: string; - rp_per_bird: number; - rp_per_kg: number; - amount: number; -} - -export interface HppPurchasesSummary { +export interface HppItem { + id: number; + category: string; + code: string; label: string; budgeting: HppPurchaseDataAmount; realization: HppPurchaseDataAmount; } -export interface HppPurchaseData { - type: string; +export interface HppSummary { + label: string; budgeting: HppPurchaseDataAmount; realization: HppPurchaseDataAmount; + egg_budgeting: HppPurchaseDataAmount; + egg_realization: HppPurchaseDataAmount; } export interface HppPurchaseDataAmount { @@ -285,8 +251,27 @@ export interface HppPurchaseDataAmount { amount: number; } -export interface DataSummarySubTotal { +export interface ClosingFinanceProfitLoss { + items: ProfitLossItem[]; + summary: ProfitLossSummary; +} + +export interface ProfitLossItem { + code: string; label: string; + type: string; + rp_per_bird: number; + rp_per_kg: number; + amount: number; +} + +export interface ProfitLossSummary { + gross_profit: ProfitLossAmount; + sub_total: ProfitLossAmount; + net_profit: ProfitLossAmount; +} + +export interface ProfitLossAmount { rp_per_bird: number; rp_per_kg: number; amount: number; From 01b8841e3cbbe94c1b5b4fdeb4fd41b8fb9ac278 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 21:14:55 +0700 Subject: [PATCH 102/169] fix(FE): adjust ui closing overhead kandang --- .../pages/closing/ClosingOverheadTable.tsx | 241 +++++++++++------- 1 file changed, 150 insertions(+), 91 deletions(-) diff --git a/src/components/pages/closing/ClosingOverheadTable.tsx b/src/components/pages/closing/ClosingOverheadTable.tsx index ed74ca66..9ef6694f 100644 --- a/src/components/pages/closing/ClosingOverheadTable.tsx +++ b/src/components/pages/closing/ClosingOverheadTable.tsx @@ -32,101 +32,160 @@ const ClosingOverheadTable = ({ ); // Helper function to create columns with footer support - const createColumns = (total?: OverheadTotal): ColumnDef[] => [ - // Group untuk kolom tanpa footer - { - header: 'Nama Item', - accessorFn: (props) => props.item_name, - footer: 'Total Pengeluaran Overhead', - }, - { - header: 'Satuan', - accessorFn: (props) => props.uom_name, - }, - { - header: 'Budget Pengajuan', - footer: '', - columns: [ - { - id: 'budget_quantity', - header: 'Jumlah', - accessorFn: (props) => - props.budget_quantity ? formatNumber(props.budget_quantity) : '-', - footer: total ? () => formatNumber(total.budget_quantity) : '', - }, - { - id: 'budget_unit_price', - header: 'Harga Satuan', - accessorFn: (props) => - props.budget_unit_price - ? formatCurrency(props.budget_unit_price) - : '-', - footer: '', - }, - { - id: 'budget_total_amount', - header: 'Total', - accessorFn: (props) => - props.budget_total_amount - ? formatCurrency(props.budget_total_amount) - : '-', - footer: total ? () => formatCurrency(total.budget_total_amount) : '', - }, - ], - }, - { - header: 'Realisasi', - footer: '', - columns: [ - { - id: 'actual_date', - header: 'Tanggal', - accessorFn: (props) => - props.actual_date - ? formatDate(props.actual_date, 'DD MMM, YYYY') - : '-', - footer: '', - }, - { - id: 'actual_quantity', - header: 'Jumlah', - accessorFn: (props) => - props.actual_quantity ? formatNumber(props.actual_quantity) : '-', - footer: total ? () => formatNumber(total.actual_quantity) : '', - }, - { - id: 'actual_unit_price', - header: 'Harga Satuan', - accessorFn: (props) => - props.actual_unit_price - ? formatCurrency(props.actual_unit_price) - : '-', - footer: '', - }, - { - id: 'actual_total_amount', - header: 'Total', - accessorFn: (props) => - props.actual_total_amount - ? formatCurrency(props.actual_total_amount) - : '-', - footer: total ? () => formatCurrency(total.actual_total_amount) : '', - }, - ], - }, - { - id: 'cost_per_bird', - header: 'Rp/Ekor', - accessorFn: (props) => - props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-', - footer: total ? () => formatCurrency(total.cost_per_bird) : '', - }, - ]; + const createColumns = ( + total?: OverheadTotal, + kandangId?: number + ): ColumnDef[] => { + const flockColumn: ColumnDef[] = [ + { + header: 'Budget Pengajuan', + footer: '', + columns: [ + { + id: 'budget_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.budget_quantity ? formatNumber(props.budget_quantity) : '-', + footer: total ? () => formatNumber(total.budget_quantity) : '', + }, + { + id: 'budget_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.budget_unit_price + ? formatCurrency(props.budget_unit_price) + : '-', + footer: '', + }, + { + id: 'budget_total_amount', + header: 'Total', + accessorFn: (props) => + props.budget_total_amount + ? formatCurrency(props.budget_total_amount) + : '-', + footer: total + ? () => formatCurrency(total.budget_total_amount) + : '', + }, + ], + }, + { + header: 'Realisasi', + footer: '', + columns: [ + { + id: 'actual_date', + header: 'Tanggal', + accessorFn: (props) => + props.actual_date + ? formatDate(props.actual_date, 'DD MMM, YYYY') + : '-', + footer: '', + }, + { + id: 'actual_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.actual_quantity ? formatNumber(props.actual_quantity) : '-', + footer: total ? () => formatNumber(total.actual_quantity) : '', + }, + { + id: 'actual_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.actual_unit_price + ? formatCurrency(props.actual_unit_price) + : '-', + footer: '', + }, + { + id: 'actual_total_amount', + header: 'Total', + accessorFn: (props) => + props.actual_total_amount + ? formatCurrency(props.actual_total_amount) + : '-', + footer: total + ? () => formatCurrency(total.actual_total_amount) + : '', + }, + ], + }, + ]; + + const kandangColumn: ColumnDef[] = [ + { + id: 'actual_date', + header: 'Tanggal', + accessorFn: (props) => + props.actual_date + ? formatDate(props.actual_date, 'DD MMM, YYYY') + : '-', + footer: '', + }, + { + id: 'actual_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.actual_quantity ? formatNumber(props.actual_quantity) : '-', + footer: total ? () => formatNumber(total.actual_quantity) : '', + }, + { + id: 'actual_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.actual_unit_price + ? formatCurrency(props.actual_unit_price) + : '-', + footer: '', + }, + { + id: 'actual_total_amount', + header: 'Total', + accessorFn: (props) => + props.actual_total_amount + ? formatCurrency(props.actual_total_amount) + : '-', + footer: total ? () => formatCurrency(total.actual_total_amount) : '', + }, + ]; + + const finalColumns: ColumnDef[] = [ + // Group untuk kolom tanpa footer + { + header: 'No', + accessorFn: (_, index) => index, + cell: (props) => props.row.index + 1, + }, + { + header: 'Nama Item', + accessorFn: (props) => props.item_name, + footer: 'Total Pengeluaran Overhead', + }, + { + header: 'Satuan', + accessorFn: (props) => props.uom_name, + }, + ...(kandangId ? kandangColumn : flockColumn), + { + id: 'cost_per_bird', + header: 'Rp/Ekor', + accessorFn: (props) => + props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-', + footer: total ? () => formatCurrency(total.cost_per_bird) : '', + }, + ]; + return finalColumns; + }; const columns = useMemo( () => isResponseSuccess(overhead) - ? createColumns(overhead.data?.total) + ? createColumns( + overhead.data?.total, + kandangId ? Number(kandangId) : undefined + ) : createColumns(), [overhead] ); From 9a3617edf1a22987d772f0a0b259150ba921608d Mon Sep 17 00:00:00 2001 From: randy-ar Date: Fri, 16 Jan 2026 21:04:23 +0700 Subject: [PATCH 103/169] fix(FE): hot fix closing finance per kandang --- src/components/pages/closing/ClosingFinanceTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/pages/closing/ClosingFinanceTable.tsx b/src/components/pages/closing/ClosingFinanceTable.tsx index 0f574c75..0d4aea5f 100644 --- a/src/components/pages/closing/ClosingFinanceTable.tsx +++ b/src/components/pages/closing/ClosingFinanceTable.tsx @@ -112,7 +112,6 @@ const ClosingFinanceTable = ({
- {JSON.stringify(finance)} Date: Fri, 16 Jan 2026 21:11:46 +0700 Subject: [PATCH 104/169] fix(FE): delete console log debug --- src/app/finance/detail/page.tsx | 2 -- .../pages/dashboard/DashboardProduction.tsx | 5 ---- .../project-flock/form/ProjectFlockForm.tsx | 3 -- .../pages/report/DailyMarketingsTable.tsx | 2 +- .../daily-checklist/DailyChecklistContent.tsx | 30 +++++++++---------- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/app/finance/detail/page.tsx b/src/app/finance/detail/page.tsx index 1d20e9f5..b80e8acb 100644 --- a/src/app/finance/detail/page.tsx +++ b/src/app/finance/detail/page.tsx @@ -24,8 +24,6 @@ const FinanceDetailPage = () => { ); } - console.log(finance); - // if (!finance || isResponseError(finance)) { // router.replace('/404'); // return; diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index cf5eeaa2..0175a8be 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -118,8 +118,6 @@ const DashboardProduction = () => { } as DashboardFilterType, validationSchema: getDashboardFilterSchema(analysisMode), onSubmit: (values) => { - console.log(values); - handleApplyFilter({ start_date: values.startDate || '', end_date: values.endDate || '', @@ -139,8 +137,6 @@ const DashboardProduction = () => { }; const handleApplyFilter = (values: DashboardFilter) => { - console.log(values); - // Build query params object, only include non-empty values const params: Record = {}; @@ -156,7 +152,6 @@ const DashboardProduction = () => { if (values.comparison_type) params.comparison_type = values.comparison_type; setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`); - console.log(endpointUrl); filterModal.closeModal(); refreshDashboardProductionData(); }; diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index 0c252cb6..d032b67c 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -557,15 +557,12 @@ const ProjectFlockForm = ({ }; const onDeleteBudgetRowHandler = (nonstock_id: number, index?: number) => { - console.log(`nonstock_id: ${nonstock_id}, index: ${index}`); if (!nonstock_id) { const updatedBudgets = formik.values.project_budgets .map((budget, i) => { if (i == index) { - console.log(`buget: ${null}, index: ${index}, i: ${i}`); return null; } else { - console.log(`buget: ${budget}, index: ${index}, i: ${i}`); return budget; } }) diff --git a/src/components/pages/report/DailyMarketingsTable.tsx b/src/components/pages/report/DailyMarketingsTable.tsx index 2dba0309..d270d6d7 100644 --- a/src/components/pages/report/DailyMarketingsTable.tsx +++ b/src/components/pages/report/DailyMarketingsTable.tsx @@ -168,7 +168,7 @@ const DailyMarketingsTable = ({ ]; useEffect(() => { - console.log({ sorting }); + // console.log({ sorting }); if (sorting.length === 1) { onFilterByChange(sorting[0].id); diff --git a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx index 7bd0be83..314381fd 100644 --- a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx +++ b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx @@ -601,15 +601,15 @@ export function DailyChecklistContent() { ) => { const taskId = taskIdsByPhaseActivityId[activityId]; - console.log('[CHECKBOX] Click detected:', { - activityId, - employeeId, - checked, - taskId, - hasTaskId: !!taskId, - checklistStatus, - isEditable, - }); + // console.log('[CHECKBOX] Click detected:', { + // activityId, + // employeeId, + // checked, + // taskId, + // hasTaskId: !!taskId, + // checklistStatus, + // isEditable, + // }); if (!taskId) { console.error('[CHECKBOX] No taskId found for activityId:', activityId); @@ -638,10 +638,10 @@ export function DailyChecklistContent() { }, }, }; - console.log( - '[CHECKBOX] State updated optimistically:', - updated[taskId]?.[employeeId] - ); + // console.log( + // '[CHECKBOX] State updated optimistically:', + // updated[taskId]?.[employeeId] + // ); return updated; }); @@ -653,7 +653,7 @@ export function DailyChecklistContent() { note: assignments[taskId]?.[employeeId]?.note || null, }; - console.log('[CHECKBOX] Saving to database:', payload); + // console.log('[CHECKBOX] Saving to database:', payload); const checkOrUncheckAssignmentRes = await DailyChecklistApi.checkOrUncheckAssignment(payload); @@ -679,7 +679,7 @@ export function DailyChecklistContent() { return; } - console.log('[CHECKBOX] Saved successfully'); + // console.log('[CHECKBOX] Saved successfully'); }; const handleNoteChange = async ( From f371d063863336753817b3463bae9a536c5971a5 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Fri, 16 Jan 2026 21:55:39 +0700 Subject: [PATCH 105/169] fix(FE): add depedency to useMemo rows data --- src/components/pages/closing/ClosingFinanceTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/closing/ClosingFinanceTable.tsx b/src/components/pages/closing/ClosingFinanceTable.tsx index 0d4aea5f..6225f5e7 100644 --- a/src/components/pages/closing/ClosingFinanceTable.tsx +++ b/src/components/pages/closing/ClosingFinanceTable.tsx @@ -44,7 +44,7 @@ const ClosingFinanceTable = ({ return [customItems, ...purchases, totalBudgeting, ...overheads]; } return []; - }, []); + }, [finance]); const profitLossTableData: ProfitLossItem[] = useMemo(() => { if (isResponseSuccess(finance)) { @@ -78,7 +78,7 @@ const ClosingFinanceTable = ({ return [...incomes, ...purchases, grossProfit, ...overheads, subtotal]; } return []; - }, []); + }, [finance]); return (
From c55081f358a9ce9e683adbc74ac4f8fc2ef4c1a6 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 17 Jan 2026 09:48:37 +0700 Subject: [PATCH 106/169] fix(FE): fix limit fetch data kandang --- .../pages/production/project-flock/form/ProjectFlockForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index d032b67c..89769787 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -149,7 +149,7 @@ const ProjectFlockForm = ({ const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ search: '', location_id: selectedLocation == '' ? '0' : selectedLocation, - limit: 'limit', + limit: '500', }).toString()}`; const { data: kandang, From a26919f0370e3385b1fcc59a56fbbe9e9267cded Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 17 Jan 2026 10:49:41 +0700 Subject: [PATCH 107/169] fix(FE): show server error --- .../pages/master-data/flock/FlocksTable.tsx | 32 +++++----- .../master-data/flock/form/FlockForm.tsx | 64 ++++++++++++++----- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx index baeaa69e..dd6ebfe8 100644 --- a/src/components/pages/master-data/flock/FlocksTable.tsx +++ b/src/components/pages/master-data/flock/FlocksTable.tsx @@ -33,22 +33,6 @@ const RowsOptions = ({ }) => { return ( - - - + + + )} {formType !== 'detail' && (
{
)}
- - {flockFormErrorMessage && ( -
- - {flockFormErrorMessage} -
- )} From 13abc6d7ce3003ad913c313cf7283b994b9c721b Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 17 Jan 2026 11:02:22 +0700 Subject: [PATCH 108/169] fix(FE): change status badge --- .../project-flock/ProjectFlockTable.tsx | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 6b935812..ab14ef84 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -15,7 +15,7 @@ import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWith import Table from '@/components/Table'; import { ROWS_OPTIONS } from '@/config/constant'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { cn, formatDate } from '@/lib/helper'; +import { cn, formatDate, formatTitleCase } from '@/lib/helper'; import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data'; import { ProjectFlockApi } from '@/services/api/production/project-flock'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -254,7 +254,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { header: 'Status', cell: (props) => { const approval = props.row.original.approval; - + const isRejected = approval?.action == 'REJECTED'; + const isApproved = approval?.action == 'APPROVED'; return ( void }) => { badge: 'rounded-lg px-2 w-full flex flex-row justify-start', }} color={ - approval?.step_number == 1 - ? 'neutral' - : approval?.step_number == 2 - ? 'success' - : 'error' + isRejected + ? 'error' + : isApproved + ? approval?.step_number == 1 + ? 'neutral' + : approval?.step_number == 2 + ? 'primary' + : approval?.step_number == 3 + ? 'success' + : 'neutral' + : 'neutral' } > void }) => { approval?.step_number == 1 ? 'neutral' : approval?.step_number == 2 - ? 'success' - : 'error' + ? 'primary' + : approval?.step_number == 3 + ? 'success' + : 'neutral' } /> - {approval?.step_name} + {isRejected + ? 'Ditolak' + : formatTitleCase(approval?.step_name || '')} ); }, From 835ba077d8e1176397259d9ca3b8c25a3510f748 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 17 Jan 2026 11:08:25 +0700 Subject: [PATCH 109/169] fix(FE): change status badge kandang aktif --- .../production/chickin/form/ChickinForm.tsx | 4 ++-- .../project-flock/detail/ProjectFlockDetail.tsx | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx index b5b1dc4d..d484e1c6 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.tsx +++ b/src/components/pages/production/chickin/form/ChickinForm.tsx @@ -75,12 +75,12 @@ const ChickinFormKandang = ({
- {' '} + {' '} Aktif
diff --git a/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx b/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx index 4a998c83..50b258fb 100644 --- a/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx +++ b/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx @@ -156,9 +156,9 @@ const ProjectFlockDetail = ({ projectFlock.approval?.step_number == 1 ? 'neutral' : projectFlock.approval?.step_number == 2 - ? 'success' - : projectFlock.approval?.step_number >= 3 - ? 'error' + ? 'primary' + : projectFlock.approval?.step_number == 3 + ? 'success' : undefined } className={{ @@ -173,9 +173,9 @@ const ProjectFlockDetail = ({ projectFlock.approval?.step_number == 1 ? 'neutral' : projectFlock.approval?.step_number == 2 - ? 'success' - : projectFlock.approval?.step_number >= 3 - ? 'error' + ? 'primary' + : projectFlock.approval?.step_number == 3 + ? 'success' : undefined } />{' '} @@ -273,7 +273,7 @@ const ProjectFlockDetail = ({
{' '} Kandang Aktif ({projectFlock.kandangs?.length}) From 4c4c70e10f84efd4a55331a144abc9c8b2c970dd Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:03:32 +0700 Subject: [PATCH 110/169] refactor(FE): Validate duplicates by selected record date --- .../recording/form/RecordingForm.schema.ts | 2 +- .../recording/form/RecordingForm.tsx | 87 ++++++++++++++++--- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 8ebd4aa2..18dc9823 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -104,7 +104,7 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema; diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index b45f72d0..3e40b404 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -56,6 +56,7 @@ import { import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Kandang } from '@/types/api/master-data/kandang'; +import * as Yup from 'yup'; import { RecordingGrowingFormSchema, RecordingLayingFormSchema, @@ -231,6 +232,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [, setNewRecordingData] = useState(null); const [nextDayRecording, setNextDayRecording] = useState(null); + const [currentRecordDate, setCurrentRecordDate] = useState( + new Date().toISOString().split('T')[0] + ); + const [duplicateErrorShown, setDuplicateErrorShown] = useState(false); const approveModal = useModal(); const rejectModal = useModal(); @@ -719,18 +724,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const recordedProjectFlockKandangIds = useMemo(() => { if (!isResponseSuccess(existingRecordings)) return new Set(); - const todayRecordings = existingRecordings.data; + const allRecordings = existingRecordings.data; const recordedIds = new Set(); - todayRecordings.forEach((recording) => { + allRecordings.forEach((recording) => { const recordingDate = recording.record_datetime?.split('T')[0]; - if (recordingDate === today) { + if (recordingDate === currentRecordDate) { recordedIds.add(recording.project_flock?.project_flock_kandang_id); } }); return recordedIds; - }, [existingRecordings, today]); + }, [existingRecordings, currentRecordDate]); const currentTotalChickQty = useMemo(() => { if (type === 'add' && projectFlockKandangLookup) { @@ -910,14 +915,38 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { initialValues: formikInitialValues, enableReinitialize: true, validationSchema: (() => { + let schema; if (isLayingCategory) { - return type === 'edit' - ? UpdateRecordingLayingFormSchema - : RecordingLayingFormSchema; + schema = + type === 'edit' + ? UpdateRecordingLayingFormSchema + : RecordingLayingFormSchema; + } else { + schema = + type === 'edit' + ? UpdateRecordingGrowingFormSchema + : RecordingGrowingFormSchema; } - return type === 'edit' - ? UpdateRecordingGrowingFormSchema - : RecordingGrowingFormSchema; + return schema.clone().concat( + Yup.object().shape({ + project_flock_kandang_id: Yup.number().test( + 'not-already-recorded', + 'Project Flock ini sudah direcord pada tanggal tersebut!', + function (value) { + if (type !== 'add') return true; + if (value && recordedProjectFlockKandangIds.has(value)) { + if (this.createError) { + return this.createError({ + message: `Project Flock ini sudah direcord pada tanggal ${formatDate(currentRecordDate, 'DD MMMM YYYY')}!`, + }); + } + return false; + } + return true; + } + ), + }) + ); })(), validateOnChange: true, validateOnBlur: true, @@ -1149,6 +1178,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedLocation(location); setSelectedProjectFlock(null); setSelectedKandang(null); + if (duplicateErrorShown) { + toast.dismiss(); + setDuplicateErrorShown(false); + } setSelectedProjectFlockLocationId( location ? location.value.toString() : '' ); @@ -1159,6 +1192,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const projectFlockChangeHandler = (val: OptionType | OptionType[] | null) => { setSelectedProjectFlock(val as OptionType); setSelectedKandang(null); + if (duplicateErrorShown) { + toast.dismiss(); + setDuplicateErrorShown(false); + } formik.setFieldValue('project_flock_kandang', null); formik.setFieldValue('project_flock_kandang_id', 0); }; @@ -1166,6 +1203,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { const kandang = val as OptionType; setSelectedKandang(kandang); + if (duplicateErrorShown) { + toast.dismiss(); + setDuplicateErrorShown(false); + } if (selectedLocation && kandang) { setStockProductsLocationId(selectedLocation.value.toString()); setStockProductsKandangId(kandang.value.toString()); @@ -1187,9 +1228,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const handleRecordDateChange = useCallback( (e: React.ChangeEvent) => { - formik.setFieldValue('record_date', e.target.value); + const newDate = e.target.value; + formik.setFieldValue('record_date', newDate); + setCurrentRecordDate(newDate); + if (duplicateErrorShown) { + toast.dismiss(); + setDuplicateErrorShown(false); + } + setTimeout(() => { + formik.validateField('project_flock_kandang_id'); + }, 0); }, - [formik] + [formik, duplicateErrorShown] ); useEffect(() => { @@ -1199,8 +1249,19 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { if (type === 'add') { if (recordedProjectFlockKandangIds.has(projectFlockKandangId)) { - toast.error('Project Flock Kandang ini sudah direcord hari ini!'); + if (!duplicateErrorShown) { + toast.error( + `Project Flock Kandang ini sudah direcord pada tanggal ${formatDate(currentRecordDate, 'DD MMMM YYYY')}!`, + { duration: Infinity } + ); + setDuplicateErrorShown(true); + } return; + } else { + if (duplicateErrorShown) { + toast.dismiss(); + setDuplicateErrorShown(false); + } } if ( From cd9fa31ad71415fb109284080dcf7a18502b4ed8 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:11:30 +0700 Subject: [PATCH 111/169] refactor(FE): Disable form fields in edit mode --- .../pages/production/recording/form/RecordingForm.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 3e40b404..106fc220 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1660,6 +1660,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Boolean(formik.errors.record_date) } errorMessage={formik.errors.record_date as string} + disabled={type === 'edit'} /> { placeholder='Pilih Lokasi' isClearable isSearchable + isDisabled={type === 'edit'} /> { onInputChange={setProjectFlockSearchValue} isLoading={isLoadingProjectFlocks} onMenuScrollToBottom={loadMoreProjectFlocks} - isDisabled={!selectedLocation} + isDisabled={!selectedLocation || type === 'edit'} placeholder={ selectedLocation ? 'Pilih Project Flock' @@ -1704,7 +1706,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { onChange={kandangChangeHandler} options={kandangOptions} isLoading={false} - isDisabled={!selectedProjectFlock} + isDisabled={!selectedProjectFlock || type === 'edit'} placeholder={ selectedProjectFlock ? 'Pilih Kandang' From f32b77c5527bfbda7f64eeae60db77b1b1c90ded Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:24:32 +0700 Subject: [PATCH 112/169] refactor(FE): Add location, project_flock and kandang fields --- .../recording/form/RecordingForm.schema.ts | 45 ++++++++++++++++++ .../recording/form/RecordingForm.tsx | 47 ++++++++++++++++--- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 18dc9823..59d5ac43 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -8,6 +8,21 @@ import { type RecordingGrowingFormSchemaType = { record_date: string; + location?: { + value: number; + label: string; + } | null; + location_id: number; + project_flock?: { + value: number; + label: string; + } | null; + project_flock_id: number; + kandang?: { + value: number; + label: string; + } | null; + kandang_id: number; project_flock_kandang: { value: number; label: string; @@ -89,6 +104,30 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema { // ===== EVENT HANDLERS ===== const locationChangeHandler = (val: OptionType | OptionType[] | null) => { - const location = val as OptionType; + const location = val as OptionType | null; + const locationId = Number(location?.value); + + formik.setFieldTouched('location', true); + formik.setFieldValue('location', location); + formik.setFieldTouched('location_id', true); + formik.setFieldValue('location_id', locationId); + setSelectedLocation(location); setSelectedProjectFlock(null); setSelectedKandang(null); @@ -1185,23 +1192,34 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { setSelectedProjectFlockLocationId( location ? location.value.toString() : '' ); - formik.setFieldValue('project_flock_kandang', null); - formik.setFieldValue('project_flock_kandang_id', 0); }; const projectFlockChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedProjectFlock(val as OptionType); + const projectFlock = val as OptionType | null; + const projectFlockId = Number(projectFlock?.value); + + formik.setFieldTouched('project_flock', true); + formik.setFieldValue('project_flock', projectFlock); + formik.setFieldTouched('project_flock_id', true); + formik.setFieldValue('project_flock_id', projectFlockId); + + setSelectedProjectFlock(projectFlock); setSelectedKandang(null); if (duplicateErrorShown) { toast.dismiss(); setDuplicateErrorShown(false); } - formik.setFieldValue('project_flock_kandang', null); - formik.setFieldValue('project_flock_kandang_id', 0); }; const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { - const kandang = val as OptionType; + const kandang = val as OptionType | null; + const kandangId = Number(kandang?.value); + + formik.setFieldTouched('kandang', true); + formik.setFieldValue('kandang', kandang); + formik.setFieldTouched('kandang_id', true); + formik.setFieldValue('kandang_id', kandangId); + setSelectedKandang(kandang); if (duplicateErrorShown) { toast.dismiss(); @@ -1676,6 +1694,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { isClearable isSearchable isDisabled={type === 'edit'} + isError={ + formik.touched.location_id && + Boolean(formik.errors.location_id) + } + errorMessage={formik.errors.location_id as string} /> { } isClearable isSearchable + isError={ + formik.touched.project_flock_id && + Boolean(formik.errors.project_flock_id) + } + errorMessage={formik.errors.project_flock_id as string} /> { } isClearable isSearchable + isError={ + formik.touched.kandang_id && + Boolean(formik.errors.kandang_id) + } + errorMessage={formik.errors.kandang_id as string} startAdornment={ projectFlockKandangLookup || projectFlockKandangDetail ? getProjectFlockBadgeAdornment() From 0cdbff69546d9c68b94c04c4ebf906d6e26d7c4a Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:32:55 +0700 Subject: [PATCH 113/169] refactor(FE): Validate recording date and handle null location --- .../pages/production/recording/form/RecordingForm.schema.ts | 1 + .../pages/production/recording/form/RecordingForm.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 59d5ac43..82b59036 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -103,6 +103,7 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema { // ===== EVENT HANDLERS ===== const locationChangeHandler = (val: OptionType | OptionType[] | null) => { const location = val as OptionType | null; - const locationId = Number(location?.value); + const locationId = location ? Number(location.value) : 0; formik.setFieldTouched('location', true); formik.setFieldValue('location', location); From 1152b6d2c3b47f6b9a5fad3670ca30f2214ca42d Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:45:11 +0700 Subject: [PATCH 114/169] refactor(FE): Disable Submit button when duplicate error shown --- .../production/recording/form/RecordingForm.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 7f66ef6d..0995f27f 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -2990,7 +2990,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { color='primary' className='px-4' isLoading={formik.isSubmitting} - disabled={hasExceededStock || formik.isSubmitting} + disabled={ + hasExceededStock || + formik.isSubmitting || + duplicateErrorShown + } > Submit @@ -3015,7 +3019,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { color='primary' className='px-4' isLoading={formik.isSubmitting} - disabled={hasExceededStock || formik.isSubmitting} + disabled={ + hasExceededStock || + formik.isSubmitting || + duplicateErrorShown + } > Submit From f4abfd4279c9f72a6c140d43e630848f73158be5 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:54:16 +0700 Subject: [PATCH 115/169] refactor(FE): Dismiss toast notifications on unmount --- .../pages/production/recording/form/RecordingForm.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 0995f27f..d7f913e8 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -237,6 +237,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ); const [duplicateErrorShown, setDuplicateErrorShown] = useState(false); + useEffect(() => { + return () => { + toast.dismiss(); + }; + }, []); + const approveModal = useModal(); const rejectModal = useModal(); const deleteModal = useModal(); From d19b1e885e74769bc2758d8681f18d46ebd0f7af Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sat, 17 Jan 2026 12:59:41 +0700 Subject: [PATCH 116/169] refactor(FE): Compute item total on qty change and limit reset --- .../order/PurchaseOrderStaffApprovalForm.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx index 729b6782..a232347d 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx @@ -633,8 +633,18 @@ const PurchaseOrderStaffApprovalForm = ({ formik.setFieldValue(`items.${idx}.qty`, numValue); - formik.setFieldValue(`items.${idx}.price`, ''); - formik.setFieldValue(`items.${idx}.total_price`, ''); + if ( + formItem.price !== '' && + formItem.price !== undefined && + formItem.price !== null && + numValue !== '' && + numValue > 0 + ) { + const calculatedTotal = Number(formItem.price) * Number(numValue); + formik.setFieldValue(`items.${idx}.total_price`, calculatedTotal); + } else if (numValue === '') { + formik.setFieldValue(`items.${idx}.total_price`, ''); + } } if (field === 'price' || field === 'total_price') { @@ -1184,8 +1194,10 @@ const PurchaseOrderStaffApprovalForm = ({ color='warning' className='px-4' onClick={() => { - formik.setValues(formikInitialValues); - formik.resetForm(); + if (type === 'add') { + formik.setValues(formikInitialValues); + formik.resetForm(); + } setPurchaseOrderFormErrorMessage(''); onCancel?.(); onModalClose?.(); From 138ad6a7c91f8c590919958707d518c1b2c38ff1 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 17 Jan 2026 15:59:08 +0700 Subject: [PATCH 117/169] hotfix(FE): change select input component to disabled --- .../product/detail/InventoryProductDetail.tsx | 2 +- .../pages/marketing/MarketingTable.tsx | 50 ++++++++++++++++++- .../marketing/detail/MarketingDetail.tsx | 47 ++++++++++++++++- .../pages/marketing/form/MarketingForm.tsx | 4 +- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx b/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx index 42c177d7..39609b06 100644 --- a/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx +++ b/src/components/pages/inventory/product/detail/InventoryProductDetail.tsx @@ -91,7 +91,7 @@ const InventoryProductDetail = ({ : {inventoryProduct?.tax - ? formatCurrency(inventoryProduct?.tax) + ? formatNumber(inventoryProduct?.tax) + '%' : '-'} diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index 467c2e00..8f1a6cf9 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -16,7 +16,7 @@ import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector'; import { TableToolbar } from '@/components/table/TableToolbar'; import { ROWS_OPTIONS } from '@/config/constant'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { cn, formatCurrency, formatDate } from '@/lib/helper'; +import { cn, formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { MarketingApi, SalesOrderApi, @@ -33,6 +33,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { useAuth } from '@/services/hooks/useAuth'; import { CustomerApi, ProductApi } from '@/services/api/master-data'; import { MARKETING_APPROVAL_LINE } from '@/config/approval-line'; +import Badge from '@/components/Badge'; const RowsOptionsMenu = ({ type = 'dropdown', @@ -520,8 +521,53 @@ const MarketingTable = () => { }, }, { - accessorKey: 'latest_approval.step_name', + accessorKey: 'approval.step_name', header: 'Status', + cell: (props) => { + const approval = props.row.original.latest_approval; + const isRejected = approval?.action == 'REJECTED'; + const isApproved = approval?.action == 'APPROVED'; + return ( + + + {isRejected + ? 'Ditolak' + : formatTitleCase(approval?.step_name || '')} + + ); + }, }, { accessorKey: 'customer.name', diff --git a/src/components/pages/marketing/detail/MarketingDetail.tsx b/src/components/pages/marketing/detail/MarketingDetail.tsx index 12ebda20..91e02928 100644 --- a/src/components/pages/marketing/detail/MarketingDetail.tsx +++ b/src/components/pages/marketing/detail/MarketingDetail.tsx @@ -16,6 +16,7 @@ import { formatCurrency, formatDate, formatNumber, + formatTitleCase, formatVechicleNumber, } from '@/lib/helper'; import { @@ -34,6 +35,7 @@ import toast from 'react-hot-toast'; import SalesOrderExport from '@/components/pages/marketing/pdf/SalesOrderExport'; import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport'; import RequirePermission from '@/components/helper/RequirePermission'; +import Badge from '@/components/Badge'; const MarketingDetail = ({ initialValues, @@ -121,6 +123,10 @@ const MarketingDetail = ({ ); }; + const approval = initialValues?.latest_approval; + const isRejected = approval?.action == 'REJECTED'; + const isApproved = approval?.action == 'APPROVED'; + return ( <>
@@ -230,7 +236,46 @@ const MarketingDetail = ({ Status : - {initialValues?.latest_approval?.step_name} + + + + {isRejected + ? 'Ditolak' + : formatTitleCase(approval?.step_name || '')} + + Tanggal Penjualan diff --git a/src/components/pages/marketing/form/MarketingForm.tsx b/src/components/pages/marketing/form/MarketingForm.tsx index be4367cb..f20a44b5 100644 --- a/src/components/pages/marketing/form/MarketingForm.tsx +++ b/src/components/pages/marketing/form/MarketingForm.tsx @@ -621,7 +621,9 @@ const MarketingForm = ({ isClearable placeholder='Pilih Pelanggan' isDisabled={ - formType === 'add_deliver' || formType === 'edit_deliver' + formType === 'add_deliver' || + formType === 'edit_deliver' || + formType === 'edit' } /> Date: Sat, 17 Jan 2026 16:16:34 +0700 Subject: [PATCH 118/169] refactor(FE): Rename price to unit_price in customer payments --- .../report/finance/export/CustomerPaymentExportPDF.tsx | 4 ++-- .../report/finance/export/CustomerPaymentExportXLSX.tsx | 6 +++--- .../pages/report/finance/tab/CustomerPaymentTab.tsx | 8 ++++---- src/types/api/report/customer-payment.d.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 9b1fd640..aa04b4f0 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -300,7 +300,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Rata-Rata - Harga Awal + Harga/Unit Harga Akhir @@ -378,7 +378,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatNumber(item.average_weight)} - {formatCurrency(item.price)} + {formatCurrency(item.unit_price)} {formatCurrency(item.final_price)} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index 3fb21488..830df633 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -38,7 +38,7 @@ export const generateCustomerPaymentExcel = ( 'Ekor/Qty': formatNumber(item.qty || 0), 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), - 'Harga Awal': formatCurrency(item.price || 0), + 'Harga/Unit': formatCurrency(item.unit_price || 0), 'Harga Akhir': formatCurrency(item.final_price || 0), Total: formatCurrency(item.total_price || 0), Pembayaran: formatCurrency(item.payment_amount || 0), @@ -62,7 +62,7 @@ export const generateCustomerPaymentExcel = ( 'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0), 'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0), AVG: '', - 'Harga Awal': '', + 'Harga/Unit': '', 'Harga Akhir': formatCurrency( customerReport.summary.total_final_amount || 0 ), @@ -89,7 +89,7 @@ export const generateCustomerPaymentExcel = ( { wch: 10 }, // Ekor/Qty { wch: 12 }, // Berat { wch: 10 }, // AVG - { wch: 15 }, // Harga Awal + { wch: 15 }, // Harga/Unit { wch: 15 }, // Harga Akhir { wch: 15 }, // Total { wch: 15 }, // Pembayaran diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index ef748b5f..0e3afe40 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -405,12 +405,12 @@ const CustomerPaymentTab = () => { ), }, { - id: 'price', - header: 'Harga Awal', - accessorKey: 'price', + id: 'unit_price', + header: 'Harga/Unit', + accessorKey: 'unit_price', enableSorting: false, cell: (props) => { - const value = props.row.original.price; + const value = props.row.original.unit_price; return
{formatCurrency(value)}
; }, footer: () => ( diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index 9169c99b..90834cdc 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -11,7 +11,7 @@ export type CustomerPaymentRow = { qty: number; weight: number; average_weight: number; - price: number; + unit_price: number; final_price: number; total_price: number; payment_amount: number; From 36da05890a4376ef38eb1bbba5b6f3d033ccfa56 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 17 Jan 2026 16:45:56 +0700 Subject: [PATCH 119/169] hotfix(FE): fixing failed test scenario in module finance --- .../pages/finance/FinanceDetail.tsx | 41 ++++---- src/components/pages/finance/FinanceTable.tsx | 99 +++++++++++-------- .../pages/finance/add/FormFinanceAdd.tsx | 41 ++++++-- .../FormFinanceAddInitialBalance.schema.ts | 8 +- .../FormFinanceAddInitialBalance.tsx | 29 ++++-- .../add/injection/FormFinanceInjection.tsx | 20 +++- 6 files changed, 151 insertions(+), 87 deletions(-) diff --git a/src/components/pages/finance/FinanceDetail.tsx b/src/components/pages/finance/FinanceDetail.tsx index 03291420..76d2d1a0 100644 --- a/src/components/pages/finance/FinanceDetail.tsx +++ b/src/components/pages/finance/FinanceDetail.tsx @@ -34,7 +34,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => { }, { label: 'Pihak', - value: finance.party.id ? finance.party.name : '-', + value: finance.party?.id ? finance.party?.name : '-', }, { label: 'Tanggal', @@ -56,25 +56,21 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => { }, { label: 'Nomor Rekening', - value: `${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`, + value: `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}`, }, { - label: `Rekening ${formatTitleCase(finance.party.type)}`, - value: finance.party.account_number, + label: `Rekening ${formatTitleCase(finance.party?.type)}`, + value: finance.party?.account_number, }, { label: 'Nominal', - value: formatCurrency(finance.expense_amount), - }, - { - label: 'Sisa', - value: formatCurrency(finance.income_amount), + value: formatCurrency(finance.nominal), }, ].filter((item) => { // Hide party account number row if transaction type is INJECTION if ( FINANCE_INJECTION_STATUS.includes(finance.transaction_type) && - item.label === `Rekening ${formatTitleCase(finance.party.type)}` + item.label === `Rekening ${formatTitleCase(finance.party?.type)}` ) { return false; } @@ -148,18 +144,19 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
- {FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type) && ( - - - - )} + {FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type) && + finance.party?.type !== 'SUPPLIER' && ( + + + + )} {FINANCE_INITIAL_BALANCE_STATUS.includes(finance.transaction_type) && ( - - )} + ) && + props.row.original.party?.type !== 'SUPPLIER' && ( + + + + )} {FINANCE_INITIAL_BALANCE_STATUS.includes( props.row.original.transaction_type @@ -194,20 +199,25 @@ const FinanceTable = () => { // ===== Options ===== const transactionTypeOptions = useMemo(() => { - return [ - { label: 'Transfer', value: 'TRANSFER' }, - { label: 'Cash', value: 'CASH' }, - { label: 'Card', value: 'CARD' }, - { label: 'Cheque', value: 'CHEQUE' }, - { label: 'Saldo', value: 'SALDO' }, - ]; - }, []); - const partyTypeOptions = useMemo(() => { return [ { label: 'Customer', value: 'CUSTOMER' }, { label: 'Supplier', value: 'SUPPLIER' }, ]; }, []); + const { + options: partyTypeOptions, + isLoadingOptions: partyTypeIsLoadingOptions, + setInputValue: partyTypeInputValue, + loadMore: partyTypeLoadMore, + } = useSelect( + selectedTransactionType + ? selectedTransactionType.value === 'CUSTOMER' + ? CustomerApi.basePath + : SupplierApi.basePath + : '', + 'id', + 'name' + ); const sortByOptions = useMemo(() => { return [ { label: 'Tanggal Pembayaran', value: 'payment_date' }, @@ -336,10 +346,10 @@ const FinanceTable = () => { }, { header: 'Pihak', - accessorFn: (finance: Finance) => finance.party.name, + accessorFn: (finance: Finance) => finance.party?.name, cell: (props: CellContext) => { - if (props.row.original.party.id) { - return {props.row.original.party.name}; + if (props.row.original.party?.id) { + return {props.row.original.party?.name}; } return {'-'}; }, @@ -360,12 +370,12 @@ const FinanceTable = () => { { header: 'Bank', accessorFn: (finance: Finance) => - `${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`, + `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}`, }, { header: 'Pengeluaran (Rp)', accessorFn: (finance: Finance) => - formatCurrency(finance.expense_amount), + formatCurrency(Math.abs(finance.expense_amount)), }, { header: 'Pemasukan (Rp)', @@ -468,25 +478,41 @@ const FinanceTable = () => {
+ ({ label: - bankRawData.data.find((data) => data.id === bank.value) + bankRawData.data.find((data) => data.id === bank?.value) ?.alias + ' - ' + - bankRawData.data.find((data) => data.id === bank.value) + bankRawData.data.find((data) => data.id === bank?.value) ?.account_number + ' - ' + - bankRawData.data.find((data) => data.id === bank.value) + bankRawData.data.find((data) => data.id === bank?.value) ?.owner, - value: bank.value, + value: bank?.value, })) : [] } @@ -497,13 +523,6 @@ const FinanceTable = () => { onMenuScrollToBottom={bankLoadMore} isClearable /> - { const router = useRouter(); + const [serverErrorMessage, setServerErrorMessage] = useState(''); + const [isSupplier, setIsSupplier] = useState( + initialValues?.party?.type === 'SUPPLIER' + ); // ===== Formik ===== const formikInitialValues = useMemo((): FinanceFormValues => { return { party_type_option: FINANCE_PARTY_TYPE_OPTIONS.find( - (option) => option.value === initialValues?.party.type + (option) => option.value === initialValues?.party?.type ) || null, party_id_option: initialValues?.party ? { - label: initialValues?.party.name || '', - value: initialValues?.party.id || 0, + label: initialValues?.party?.name || '', + value: initialValues?.party?.id || 0, } : null, payment_date: initialValues?.payment_date || '', @@ -72,11 +78,11 @@ const FormFinanceAdd = ({ ) || null, bank_id_option: initialValues?.bank ? { - label: initialValues.bank.name, - value: initialValues.bank.id, + label: initialValues?.bank?.name, + value: initialValues?.bank?.id, } : null, - party_account_number: initialValues?.party.account_number || '', + party_account_number: initialValues?.party?.account_number || '', reference_number: initialValues?.reference_number || '', nominal: initialValues?.nominal.toString() || '', notes: initialValues?.notes || '', @@ -153,6 +159,7 @@ const FormFinanceAdd = ({ if (isResponseError(response)) { toast.error(response.message); + setServerErrorMessage(response.message); return; } @@ -168,6 +175,7 @@ const FormFinanceAdd = ({ if (isResponseError(response)) { toast.error(response.message); + setServerErrorMessage(response.message); return; } @@ -207,6 +215,7 @@ const FormFinanceAdd = ({ ? formik.errors.party_type_option : '' } + isDisabled={type === 'edit' || isSupplier} required isClearable /> @@ -245,7 +254,7 @@ const FormFinanceAdd = ({ } required isClearable - isDisabled={!formik.values.party_type_option?.value} + isDisabled={!formik.values.party_type_option?.value || isSupplier} />