From c7ffae68d84ca7fa2f0ec85e6c080bc715696694 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Wed, 21 Jan 2026 14:27:59 +0700 Subject: [PATCH 01/13] fix(FE): adding color to negative value excel and change select UI --- package-lock.json | 24 +- src/components/input/SelectInputRadio.tsx | 70 ++++++ .../finance/export/DebtSupplierExportXLSX.tsx | 231 +++++++++++------- .../report/finance/tab/DebtSupplierTab.tsx | 6 +- 4 files changed, 223 insertions(+), 108 deletions(-) create mode 100644 src/components/input/SelectInputRadio.tsx diff --git a/package-lock.json b/package-lock.json index d108e6bb..c7a7af78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4463,7 +4463,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4474,7 +4473,6 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4547,7 +4545,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -5071,7 +5068,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6002,8 +5998,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/d3-array": { "version": "3.2.4", @@ -6430,8 +6425,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -6701,7 +6695,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6875,7 +6868,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -8508,7 +8500,6 @@ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz", "integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "fast-png": "^6.2.0", @@ -9961,7 +9952,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9992,7 +9982,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10060,8 +10049,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-number-format": { "version": "5.4.4", @@ -10078,7 +10066,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -10291,8 +10278,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -11205,7 +11191,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11391,7 +11376,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/components/input/SelectInputRadio.tsx b/src/components/input/SelectInputRadio.tsx new file mode 100644 index 00000000..73608931 --- /dev/null +++ b/src/components/input/SelectInputRadio.tsx @@ -0,0 +1,70 @@ +'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 SelectInputRadioProps + extends Omit, 'closeMenuOnSelect' | 'optionComponent'> { + closeMenuOnSelect?: boolean; +} + +const RadioOption = < + T extends OptionType, + IsMulti extends boolean, + Group extends GroupBase, +>( + props: OptionProps +) => { + const { isSelected, label, innerRef, innerProps, className } = props; + + return ( +
+ null} + className='radio radio-sm radio-primary pointer-events-none' + /> + +
+ ); +}; + +const SelectInputRadio = ( + props: SelectInputRadioProps +) => { + const { closeMenuOnSelect = true, className, ...restProps } = props; + + const customComponents = useMemo(() => { + return { + Option: RadioOption as typeof ReactSelectComponents.Option, + }; + }, []); + + return ( + + {...restProps} + closeMenuOnSelect={closeMenuOnSelect} + className={{ + ...className, + select: cn(className?.select, 'select-radio'), + }} + components={customComponents} + /> + ); +}; + +export default SelectInputRadio; diff --git a/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx b/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx index 39e0cec4..e5de3ae2 100644 --- a/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx +++ b/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx @@ -1,6 +1,6 @@ 'use client'; -import * as XLSX from 'xlsx'; +import ExcelJS from 'exceljs'; import { formatDate } from '@/lib/helper'; import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier'; @@ -8,115 +8,174 @@ interface DebtSupplierExportExcelParams { data: DebtSupplier[]; } -export const generateDebtSupplierExcel = ( +export const generateDebtSupplierExcel = async ( params: DebtSupplierExportExcelParams -): void => { +): Promise => { if (!params.data || params.data.length === 0) { return; } - const workbook = XLSX.utils.book_new(); + const workbook = new ExcelJS.Workbook(); - params.data.forEach((supplierReport) => { + const columns = [ + { header: 'No', key: 'no', width: 5 }, + { header: 'Nomor PR', key: 'prNumber', width: 14 }, + { header: 'Nomor PO', key: 'poNumber', width: 14 }, + { header: 'Tanggal Terima/Bayar', key: 'receivedDate', width: 20 }, + { header: 'Tanggal PO', key: 'poDate', width: 10 }, + { header: 'Aging (Hari)', key: 'aging', width: 10 }, + { header: 'Area', key: 'area', width: 15 }, + { header: 'Gudang', key: 'warehouse', width: 15 }, + { header: 'Jatuh Tempo', key: 'dueDate', width: 12 }, + { header: 'Status Jatuh Tempo', key: 'dueStatus', width: 20 }, + { header: 'Nominal Pembelian (Rp)', key: 'totalPrice', width: 20 }, + { header: 'Pembayaran (Rp)', key: 'paymentPrice', width: 15 }, + { header: 'Sisa Saldo Hutang (Rp)', key: 'balance', width: 20 }, + { header: 'Status', key: 'status', width: 12 }, + { header: 'Nomor Perjalanan', key: 'travelNumber', width: 15 }, + ]; + + for (const supplierReport of params.data) { const supplierData = supplierReport.rows; const supplierName = supplierReport.supplier.name || 'Unknown Supplier'; - const excelData: { [key: string]: string | number }[] = [ - { - No: '', - 'Nomor PR': '', - 'Nomor PO': '', - 'Tanggal Terima/Bayar': '', - 'Tanggal PO': '', - 'Aging (Hari)': '', - Area: '', - Gudang: '', - 'Jatuh Tempo': '', - 'Status Jatuh Tempo': '', - 'Nominal Pembelian (Rp)': '', - 'Pembayaran (Rp)': '', - 'Sisa Saldo Hutang (Rp)': supplierReport.initial_balance || 0, - Status: '', - 'Nomor Perjalanan': '', - }, - ...supplierData.map((item, index) => ({ - No: index + 1, - 'Nomor PR': item.pr_number || '', - 'Nomor PO': item.po_number || '', - 'Tanggal Terima/Bayar': item.received_date + const worksheet = workbook.addWorksheet(supplierName.substring(0, 31)); + worksheet.columns = columns; + + // Add initial balance row + const initialRow = worksheet.addRow({ + no: '', + prNumber: '', + poNumber: '', + receivedDate: '', + poDate: '', + aging: '', + area: '', + warehouse: '', + dueDate: '', + dueStatus: '', + totalPrice: '', + paymentPrice: '', + balance: supplierReport.initial_balance || 0, + status: '', + travelNumber: '', + }); + + // Apply red color if initial balance is negative + const initialBalanceCell = initialRow.getCell('balance'); + if ( + typeof supplierReport.initial_balance === 'number' && + supplierReport.initial_balance < 0 + ) { + initialBalanceCell.font = { color: { argb: 'FFFF0000' } }; + } + + // Add data rows + supplierData.forEach((item, index) => { + const row = worksheet.addRow({ + no: index + 1, + prNumber: item.pr_number || '', + poNumber: item.po_number || '', + receivedDate: item.received_date ? item.received_date != '-' ? formatDate(item.received_date, 'MM/DD/YYYY') : '-' : '-', - 'Tanggal PO': item.po_date + poDate: item.po_date ? item.po_date != '-' ? formatDate(item.po_date, 'MM/DD/YYYY') : '-' : '-', - 'Aging (Hari)': item.aging || 0, - Area: item.area?.name || '', - Gudang: item.warehouse?.name || '', - 'Jatuh Tempo': item.due_date + aging: item.aging || 0, + area: item.area?.name || '', + warehouse: item.warehouse?.name || '', + dueDate: item.due_date ? item.due_date != '-' ? formatDate(item.due_date, 'MM/DD/YYYY') : '-' : '-', - 'Status Jatuh Tempo': item.due_status || '', - 'Nominal Pembelian (Rp)': item.total_price || 0, - 'Pembayaran (Rp)': item.payment_price || 0, - 'Sisa Saldo Hutang (Rp)': item.balance || 0, - Status: item.status || '', - 'Nomor Perjalanan': item.travel_number || '', - })), - ]; - - if (supplierReport.total) { - excelData.push({ - No: 'Total', - 'Nomor PR': '', - 'Nomor PO': '', - 'Tanggal Terima/Bayar': '', - 'Tanggal PO': '', - 'Aging (Hari)': supplierReport.total.aging || 0, - Area: '', - Gudang: '', - 'Jatuh Tempo': '', - 'Status Jatuh Tempo': '', - 'Nominal Pembelian (Rp)': supplierReport.total.total_price || 0, - 'Pembayaran (Rp)': supplierReport.total.payment_price || 0, - 'Sisa Saldo Hutang (Rp)': supplierReport.total.debt_price || 0, - Status: '', - 'Nomor Perjalanan': '', + dueStatus: item.due_status || '', + totalPrice: item.total_price || 0, + paymentPrice: item.payment_price || 0, + balance: item.balance || 0, + status: item.status || '', + travelNumber: item.travel_number || '', }); + + // Apply red color for negative values + const totalPriceCell = row.getCell('totalPrice'); + if (typeof item.total_price === 'number' && item.total_price < 0) { + totalPriceCell.font = { color: { argb: 'FFFF0000' } }; + } + + const paymentPriceCell = row.getCell('paymentPrice'); + if (typeof item.payment_price === 'number' && item.payment_price < 0) { + paymentPriceCell.font = { color: { argb: 'FFFF0000' } }; + } + + const balanceCell = row.getCell('balance'); + if (typeof item.balance === 'number' && item.balance < 0) { + balanceCell.font = { color: { argb: 'FFFF0000' } }; + } + }); + + // Add total row + if (supplierReport.total) { + const totalRow = worksheet.addRow({ + no: 'Total', + prNumber: '', + poNumber: '', + receivedDate: '', + poDate: '', + aging: supplierReport.total.aging || 0, + area: '', + warehouse: '', + dueDate: '', + dueStatus: '', + totalPrice: supplierReport.total.total_price || 0, + paymentPrice: supplierReport.total.payment_price || 0, + balance: supplierReport.total.debt_price || 0, + status: '', + travelNumber: '', + }); + + // Apply red color for negative totals + const totalPriceCell = totalRow.getCell('totalPrice'); + if ( + typeof supplierReport.total.total_price === 'number' && + supplierReport.total.total_price < 0 + ) { + totalPriceCell.font = { color: { argb: 'FFFF0000' } }; + } + + const paymentPriceCell = totalRow.getCell('paymentPrice'); + if ( + typeof supplierReport.total.payment_price === 'number' && + supplierReport.total.payment_price < 0 + ) { + paymentPriceCell.font = { color: { argb: 'FFFF0000' } }; + } + + const balanceCell = totalRow.getCell('balance'); + if ( + typeof supplierReport.total.debt_price === 'number' && + supplierReport.total.debt_price < 0 + ) { + balanceCell.font = { color: { argb: 'FFFF0000' } }; + } } - - const worksheet = XLSX.utils.json_to_sheet(excelData); - - const colWidths = [ - { wch: 5 }, // No - { wch: 10 }, // Nomor PR - { wch: 10 }, // Nomor PO - { wch: 20 }, // Tanggal Terima/Bayar - { wch: 10 }, // Tanggal PO - { wch: 10 }, // Aging - { wch: 15 }, // Area - { wch: 15 }, // Gudang - { wch: 12 }, // Jatuh Tempo - { wch: 20 }, // Status Jatuh Tempo - { wch: 20 }, // Nominal Pembelian (Rp) - { wch: 15 }, // Pembayaran (Rp) - { wch: 20 }, // Sisa Saldo Hutang (Rp) - { wch: 12 }, // Status - { wch: 15 }, // Nomor Perjalanan - ]; - worksheet['!cols'] = colWidths; - - const sheetName = - supplierName.length > 31 ? supplierName.substring(0, 31) : supplierName; - XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); - }); + } const filename = `laporan-hutang-supplier-${formatDate(new Date(), 'YYYY-MM-DD-HHmm')}.xlsx`; - XLSX.writeFile(workbook, filename); + const buffer = await workbook.xlsx.writeBuffer(); + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + window.URL.revokeObjectURL(url); }; diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index 9fefa9c7..14fccffa 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -35,6 +35,8 @@ import ButtonFilter from '@/components/helper/ButtonFilter'; import Badge from '@/components/Badge'; import { Color } from '@/types/theme'; import { Supplier } from '@/types/api/master-data/supplier'; +import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; +import SelectInputRadio from '@/components/input/SelectInputRadio'; const dueStatus: Record = { 'Sudah Jatuh Tempo': 'error', @@ -671,7 +673,7 @@ const DebtSupplierTab = () => {
- {
- Date: Wed, 21 Jan 2026 15:18:37 +0700 Subject: [PATCH 02/13] fix(FE): fixing dashboard select input UI component --- src/components/input/SelectInput.tsx | 6 + src/components/input/SelectInputCheckbox.tsx | 2 +- .../pages/dashboard/DashboardProduction.tsx | 193 +++++++++++------- 3 files changed, 131 insertions(+), 70 deletions(-) diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index 9cc9fda5..2571efc5 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -312,6 +312,12 @@ const SelectInput = (props: SelectInputProps) => { } styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }), + multiValue(base) { + return { + ...base, + borderRadius: '8px', + }; + }, }} onMenuScrollToBottom={onMenuScrollToBottom} /> diff --git a/src/components/input/SelectInputCheckbox.tsx b/src/components/input/SelectInputCheckbox.tsx index 0827a70a..24125204 100644 --- a/src/components/input/SelectInputCheckbox.tsx +++ b/src/components/input/SelectInputCheckbox.tsx @@ -40,7 +40,7 @@ const CheckboxOption = < type='checkbox' checked={isSelected} onChange={() => null} - className='checkbox checkbox-sm checkbox-primary pointer-events-none' + className='checkbox checkbox-sm rounded checkbox-primary pointer-events-none' />
diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index 079b082c..dd4abd47 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -38,6 +38,8 @@ import Dropdown from '@/components/Dropdown'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; import { useDashboardStore } from '@/stores/dashboard'; +import SelectInputRadio from '@/components/input/SelectInputRadio'; +import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; // Helper function to normalize values to array const normalizeToArray = ( @@ -413,7 +415,7 @@ const DashboardProduction = () => { {formik.values.analysisMode === 'COMPARISON' && (
- option.value === formik.values.comparisonType @@ -437,32 +439,53 @@ const DashboardProduction = () => { {/* Location */}
- { - formik.setFieldValue('location', selected); - // Update selectedLocationIds for kandang filter - setSelectedLocationIds(normalizeToArray(selected)); - // Reset dependent fields when location changes - formik.setFieldValue('flock', []); - formik.setFieldValue('kandang', []); - }} - errorMessage={formik.errors.location as string} - options={locationOptions} - isLoading={isLoadingLocationOptions} - isMulti={ - comparisonTypeOptions.find( - (option) => option.value === formik.values.comparisonType - )?.value === 'FARM' - } - isError={ - Boolean(formik.errors.location) && - Boolean(formik.touched.location) - } - /> + {comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType + )?.value === 'FARM' ? ( + { + formik.setFieldValue('location', selected); + // Update selectedLocationIds for kandang filter + setSelectedLocationIds(normalizeToArray(selected)); + // Reset dependent fields when location changes + formik.setFieldValue('flock', []); + formik.setFieldValue('kandang', []); + }} + errorMessage={formik.errors.location as string} + options={locationOptions} + isLoading={isLoadingLocationOptions} + isError={ + Boolean(formik.errors.location) && + Boolean(formik.touched.location) + } + /> + ) : ( + { + formik.setFieldValue('location', selected); + // Update selectedLocationIds for kandang filter + setSelectedLocationIds(normalizeToArray(selected)); + // Reset dependent fields when location changes + formik.setFieldValue('flock', []); + formik.setFieldValue('kandang', []); + }} + errorMessage={formik.errors.location as string} + options={locationOptions} + isLoading={isLoadingLocationOptions} + isError={ + Boolean(formik.errors.location) && + Boolean(formik.touched.location) + } + /> + )}
{/* Flock */} @@ -474,27 +497,43 @@ const DashboardProduction = () => { ) ) && (
- - formik.setFieldValue('flock', selected) - } - errorMessage={formik.errors.flock as string} - onInputChange={setInputValueFlock} - onMenuScrollToBottom={loadMoreFlock} - options={flockOptions} - isLoading={isLoadingFlockOptions} - isMulti={ - comparisonTypeOptions.find( - (option) => option.value === formik.values.comparisonType - )?.value === 'FLOCK' - } - isError={ - Boolean(formik.errors.flock) && - Boolean(formik.touched.flock) - } - /> + {comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType + )?.value === 'FLOCK' ? ( + + formik.setFieldValue('flock', selected) + } + errorMessage={formik.errors.flock as string} + onInputChange={setInputValueFlock} + onMenuScrollToBottom={loadMoreFlock} + options={flockOptions} + isLoading={isLoadingFlockOptions} + isError={ + Boolean(formik.errors.flock) && + Boolean(formik.touched.flock) + } + /> + ) : ( + + formik.setFieldValue('flock', selected) + } + errorMessage={formik.errors.flock as string} + onInputChange={setInputValueFlock} + onMenuScrollToBottom={loadMoreFlock} + options={flockOptions} + isLoading={isLoadingFlockOptions} + isError={ + Boolean(formik.errors.flock) && + Boolean(formik.touched.flock) + } + /> + )}
)} @@ -504,27 +543,43 @@ const DashboardProduction = () => { !(formik.values.comparisonType === 'KANDANG') ) && (
- - formik.setFieldValue('kandang', selected) - } - errorMessage={formik.errors.kandang as string} - onInputChange={setInputValueKandang} - onMenuScrollToBottom={loadMoreKandang} - options={kandangOptions} - isLoading={isLoadingKandangOptions} - isMulti={ - comparisonTypeOptions.find( - (option) => option.value === formik.values.comparisonType - )?.value === 'KANDANG' - } - isError={ - Boolean(formik.errors.kandang) && - Boolean(formik.touched.kandang) - } - /> + {comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType + )?.value === 'KANDANG' ? ( + + formik.setFieldValue('kandang', selected) + } + errorMessage={formik.errors.kandang as string} + onInputChange={setInputValueKandang} + onMenuScrollToBottom={loadMoreKandang} + options={kandangOptions} + isLoading={isLoadingKandangOptions} + isError={ + Boolean(formik.errors.kandang) && + Boolean(formik.touched.kandang) + } + /> + ) : ( + + formik.setFieldValue('kandang', selected) + } + errorMessage={formik.errors.kandang as string} + onInputChange={setInputValueKandang} + onMenuScrollToBottom={loadMoreKandang} + options={kandangOptions} + isLoading={isLoadingKandangOptions} + isError={ + Boolean(formik.errors.kandang) && + Boolean(formik.touched.kandang) + } + /> + )}
)} From 9cffa531227d1c1b28de3925dd5ad926da349807 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 11:26:47 +0700 Subject: [PATCH 03/13] feat(FE): Show age with week in sales report table --- .../pages/closing/sale/SalesReportTable.tsx | 16 ++++++++++------ src/types/api/closing.d.ts | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index 6ddd9e4f..a5b2584a 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -112,12 +112,16 @@ const SalesReportTable = ({
Total Penjualan
), }, - // { - // id: 'age', - // accessorKey: 'age', - // header: 'Umur', - // cell: (props) => props.getValue() || '-', - // }, + { + id: 'age', + accessorKey: 'age', + header: 'Umur', + cell: (props) => { + const age = props.row.original.age; + const week = props.row.original.week; + return age && week ? `${formatNumber(age)} (Minggu ke-${week})` : '-'; + }, + }, { id: 'do_number', accessorKey: 'do_number', diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 8135c013..31a0248d 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -17,6 +17,7 @@ export type BaseSales = { id: number; realization_date: string; age: number; + week: number; do_number: string; product: Product; customer: Customer; From 1e421e4230f8bcafc4f6a1a4f34d2e677989b080 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 11:34:29 +0700 Subject: [PATCH 04/13] refactor(FE): Show age as days and weeks in table --- src/components/pages/closing/sale/SalesReportTable.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index a5b2584a..a3eb6264 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -119,7 +119,9 @@ const SalesReportTable = ({ cell: (props) => { const age = props.row.original.age; const week = props.row.original.week; - return age && week ? `${formatNumber(age)} (Minggu ke-${week})` : '-'; + return age && week + ? `${formatNumber(age)} hari (${week} minggu)` + : '-'; }, }, { From eca8bd7026eacc0667a40d601be944a2409c8497 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 11:36:02 +0700 Subject: [PATCH 05/13] refactor(FE): Display raw age instead of using formatNumber --- src/components/pages/closing/sale/SalesReportTable.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index a3eb6264..99868984 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -119,9 +119,7 @@ const SalesReportTable = ({ cell: (props) => { const age = props.row.original.age; const week = props.row.original.week; - return age && week - ? `${formatNumber(age)} hari (${week} minggu)` - : '-'; + return age && week ? `${age} hari (${week} minggu)` : '-'; }, }, { From b17ccd502ef84ed29ad26c35648443144695863f Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 22 Jan 2026 14:19:48 +0700 Subject: [PATCH 06/13] hotfix: fix sales calculation --- .../pages/dashboard/DashboardProduction.tsx | 48 ++++++++++++-- .../pages/finance/add/FormFinanceAdd.tsx | 14 +---- .../delivery-order/DeliverOrderProduct.tsx | 39 +++++++++--- .../sales-order/SalesOrderProductForm.tsx | 62 ++++++++++--------- 4 files changed, 105 insertions(+), 58 deletions(-) diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index dd4abd47..35a957c1 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -444,7 +444,13 @@ const DashboardProduction = () => { )?.value === 'FARM' ? ( { @@ -466,7 +472,13 @@ const DashboardProduction = () => { ) : ( { @@ -502,7 +514,13 @@ const DashboardProduction = () => { )?.value === 'FLOCK' ? ( formik.setFieldValue('flock', selected) } @@ -519,7 +537,13 @@ const DashboardProduction = () => { ) : ( formik.setFieldValue('flock', selected) } @@ -548,7 +572,13 @@ const DashboardProduction = () => { )?.value === 'KANDANG' ? ( formik.setFieldValue('kandang', selected) } @@ -565,7 +595,13 @@ const DashboardProduction = () => { ) : ( formik.setFieldValue('kandang', selected) } diff --git a/src/components/pages/finance/add/FormFinanceAdd.tsx b/src/components/pages/finance/add/FormFinanceAdd.tsx index 168d306a..4189521e 100644 --- a/src/components/pages/finance/add/FormFinanceAdd.tsx +++ b/src/components/pages/finance/add/FormFinanceAdd.tsx @@ -54,9 +54,6 @@ const FormFinanceAdd = ({ }: FormFinanceAddProps) => { const router = useRouter(); const [serverErrorMessage, setServerErrorMessage] = useState(''); - const [isSupplier, setIsSupplier] = useState( - initialValues?.party?.type === 'SUPPLIER' - ); // ===== Formik ===== const formikInitialValues = useMemo((): FinanceFormValues => { @@ -215,7 +212,7 @@ const FormFinanceAdd = ({ ? formik.errors.party_type_option : '' } - isDisabled={type === 'edit' || isSupplier} + isDisabled={type === 'edit'} required isClearable /> @@ -254,7 +251,7 @@ const FormFinanceAdd = ({ } required isClearable - isDisabled={!formik.values.party_type_option?.value || isSupplier} + isDisabled={!formik.values.party_type_option?.value} />