Merge branch 'fix/master-data-production-standard' into 'development'

[FIX/FE] Adding FCR Master Data Production Standard Growing

See merge request mbugroup/lti-web-client!305
This commit is contained in:
Rivaldi A N S
2026-02-03 09:32:21 +00:00
5 changed files with 92 additions and 57 deletions
+2
View File
@@ -28,6 +28,7 @@ const StatusBadge = ({
'bg-error/20': color === 'error', 'bg-error/20': color === 'error',
'bg-primary/20': color === 'info', 'bg-primary/20': color === 'info',
'bg-[#FF9A20]/12': color === 'warning', 'bg-[#FF9A20]/12': color === 'warning',
'bg-[#1166EF]/12': color === 'primary',
}, },
className?.badge className?.badge
), ),
@@ -45,6 +46,7 @@ const StatusBadge = ({
'text-error': color === 'error', 'text-error': color === 'error',
'text-primary': color === 'info', 'text-primary': color === 'info',
'text-[#FF9A20]': color === 'warning', 'text-[#FF9A20]': color === 'warning',
'text-[#1166EF]': color === 'primary',
})} })}
> >
<circle r='6' cx='6' cy='6' fill='currentColor' /> <circle r='6' cx='6' cy='6' fill='currentColor' />
@@ -250,29 +250,29 @@ const ClosingOverheadTable = ({
body: 'p-4 shadow-button-soft border border-base-content/10 rounded-lg', body: 'p-4 shadow-button-soft border border-base-content/10 rounded-lg',
}} }}
> >
<div className='flex flex-row gap-4 w-full justify-center items-stretch'> <div className='flex flex-row gap-3 w-full justify-center items-stretch'>
<div className='flex flex-row items-center justify-between'> <div className='flex flex-row items-center justify-between'>
<h2 className='text-base font-bold'>Pembelian Kandang </h2> <h2 className='text-base font-bold'>Pembelian Kandang </h2>
</div> </div>
<div className='flex flex-col items-center justify-center'> <div className='flex flex-col items-center justify-center'>
<Icon icon='heroicons:equals' className='inline' /> <Icon icon='heroicons:equals' className='inline' />
</div> </div>
<div className='flex flex-col flex-1'> <div className='flex flex-col flex-1 gap-1.5'>
<div className='flex flex-row items-center justify-center font-medium'> <div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
Populasi Akhir KANDANG{' '} Populasi Akhir KANDANG{' '}
<Icon icon='heroicons:x-mark' className='inline' /> Pemakaian <Icon icon='heroicons:x-mark' className='inline' /> Pemakaian
Di FARM Di FARM
</div> </div>
<hr className='w-full h-1' /> <hr className='w-full h-fit m-0 p-0 text-base-content/65' />
<div className='flex flex-row items-center justify-center font-medium'> <div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
Populasi Akhir Proyek Populasi Akhir Proyek
</div> </div>
</div> </div>
<div className='flex flex-col items-center justify-center'> <div className='flex flex-col items-center justify-center'>
<Icon icon='heroicons:equals' className='inline' /> <Icon icon='heroicons:equals' className='inline' />
</div> </div>
<div className='flex flex-col flex-1'> <div className='flex flex-col flex-1 gap-1.5'>
<div className='flex flex-row items-center justify-center font-medium'> <div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
{formatNumber(chickinPopulation ?? 0)} {formatNumber(chickinPopulation ?? 0)}
<Icon icon='heroicons:x-mark' className='inline' /> <Icon icon='heroicons:x-mark' className='inline' />
{formatCurrency( {formatCurrency(
@@ -281,8 +281,8 @@ const ClosingOverheadTable = ({
: 0 : 0
)} )}
</div> </div>
<hr className='w-full h-1' /> <hr className='w-full h-fit m-0 p-0 text-base-content/65' />
<div className='flex flex-row items-center justify-center font-medium'> <div className='flex flex-row gap-1.5 text-center items-center justify-center font-medium'>
{formatNumber(generalInformation?.population ?? 0)} {formatNumber(generalInformation?.population ?? 0)}
</div> </div>
</div> </div>
@@ -32,7 +32,7 @@ const GrowingRepeaterFormSchema = Yup.object({
target_hen_house_production: Yup.number().optional(), target_hen_house_production: Yup.number().optional(),
target_egg_weight: Yup.number().optional(), target_egg_weight: Yup.number().optional(),
target_egg_mass: Yup.number().optional(), target_egg_mass: Yup.number().optional(),
standard_fcr: Yup.number().optional(), standard_fcr: Yup.number().required('Wajib diisi!'),
}).optional(), }).optional(),
}); });
@@ -386,14 +386,6 @@ const ProductionStandardForm = ({
`${row.original.production_standard_details?.target_egg_mass} kg`, `${row.original.production_standard_details?.target_egg_mass} kg`,
enableSorting: false, enableSorting: false,
}, },
{
header: 'FCR',
accessorFn: (row) =>
row.production_standard_details?.standard_fcr,
cell: ({ row }) =>
`${row.original.production_standard_details?.standard_fcr} g`,
enableSorting: false,
},
] ]
: []; : [];
@@ -468,6 +460,13 @@ const ProductionStandardForm = ({
return [ return [
...baseColumns, ...baseColumns,
...productionColumns, ...productionColumns,
{
header: 'FCR',
accessorFn: (row) => row.production_standard_details?.standard_fcr,
cell: ({ row }) =>
`${row.original.production_standard_details?.standard_fcr} g`,
enableSorting: false,
},
...uniformityColumns, ...uniformityColumns,
...(formType !== 'detail' ? [actionColumn] : []), ...(formType !== 'detail' ? [actionColumn] : []),
]; ];
@@ -753,24 +752,46 @@ const ProductionStandardForm = ({
e.preventDefault(); e.preventDefault();
// For GROWING category, clear production_standard_details errors and set default values // For GROWING category, clear production_standard_details errors and set default values
// but preserve standard_fcr since it's used for both LAYING and GROWING
if (formik.values.project_category === 'GROWING') { if (formik.values.project_category === 'GROWING') {
// Set default values for production_standard_details // Set default values for production_standard_details, preserving standard_fcr
formik.values.details?.forEach((detail) => { formik.values.details?.forEach((detail) => {
detail.production_standard_details = { detail.production_standard_details = {
target_hen_day_production: 0, target_hen_day_production: 0,
target_hen_house_production: 0, target_hen_house_production: 0,
target_egg_weight: 0, target_egg_weight: 0,
target_egg_mass: 0, target_egg_mass: 0,
standard_fcr: 0, standard_fcr:
detail.production_standard_details?.standard_fcr || 0,
}; };
}); });
// Clear any errors related to production_standard_details // Clear errors only for LAYING-specific fields in production_standard_details
// Preserve standard_fcr error since it's required for both categories
const currentErrors = { ...formik.errors }; const currentErrors = { ...formik.errors };
if (currentErrors.details && Array.isArray(currentErrors.details)) { if (currentErrors.details && Array.isArray(currentErrors.details)) {
const cleanedDetails = currentErrors.details const cleanedDetails = currentErrors.details
.map((detailError) => { .map((detailError) => {
if (detailError && typeof detailError === 'object') { if (detailError && typeof detailError === 'object') {
const prodDetails = (
detailError as {
production_standard_details?: ProductionDetailsErrors;
}
).production_standard_details;
// If there's standard_fcr error, preserve it
if (prodDetails && prodDetails.standard_fcr) {
const { production_standard_details, ...rest } =
detailError;
return {
...rest,
production_standard_details: {
standard_fcr: prodDetails.standard_fcr,
},
};
}
// Otherwise, remove entire production_standard_details errors
const { production_standard_details, ...rest } = detailError; const { production_standard_details, ...rest } = detailError;
return Object.keys(rest).length > 0 ? rest : undefined; return Object.keys(rest).length > 0 ? rest : undefined;
} }
@@ -896,7 +917,7 @@ const ProductionStandardForm = ({
gridTemplateColumns: gridTemplateColumns:
formik.values.project_category === 'LAYING' formik.values.project_category === 'LAYING'
? 'repeat(10, minmax(auto, 1fr)) minmax(auto, auto)' ? 'repeat(10, minmax(auto, 1fr)) minmax(auto, auto)'
: 'repeat(4, minmax(auto, 1fr)) minmax(auto, auto)', : 'repeat(5, minmax(auto, 1fr)) minmax(auto, auto)',
}} }}
> >
<NumberInput <NumberInput
@@ -1042,20 +1063,21 @@ const ProductionStandardForm = ({
) )
} }
/> />
</>
)}
<NumberInput <NumberInput
name='production_standard_details.standard_fcr' name='production_standard_details.standard_fcr'
label='FCR' label='FCR'
placeholder='1' placeholder='1'
value={ value={
repeaterFormik.values repeaterFormik.values.production_standard_details
.production_standard_details?.standard_fcr ?.standard_fcr
} }
onChange={repeaterFormik.handleChange} onChange={repeaterFormik.handleChange}
onBlur={repeaterFormik.handleBlur} onBlur={repeaterFormik.handleBlur}
bottomLabel='Gram (g)' bottomLabel='Gram (g)'
errorMessage={getProductionDetailsError( errorMessage={getProductionDetailsError(
repeaterFormik.errors repeaterFormik.errors.production_standard_details,
.production_standard_details,
'standard_fcr' 'standard_fcr'
)} )}
isError={ isError={
@@ -1073,8 +1095,6 @@ const ProductionStandardForm = ({
) )
} }
/> />
</>
)}
<NumberInput <NumberInput
name='production_standard_uniformity_details.target_mean_bw' name='production_standard_uniformity_details.target_mean_bw'
label='Mean BW' label='Mean BW'
@@ -36,7 +36,6 @@ import SelectInputRadio from '@/components/input/SelectInputRadio';
import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton'; import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton';
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
const dueStatus: Record<string, Color> = { const dueStatus: Record<string, Color> = {
'Sudah Jatuh Tempo': 'error', 'Sudah Jatuh Tempo': 'error',
@@ -60,7 +59,15 @@ const getPillBadge = (
? dueStatus[statusText] || 'neutral' ? dueStatus[statusText] || 'neutral'
: paymentStatus[statusText] || 'neutral'; : paymentStatus[statusText] || 'neutral';
return <StatusBadge color={color as Color} text={statusText} />; return (
<StatusBadge
color={color as Color}
text={statusText}
className={{
badge: 'w-fit',
}}
/>
);
}; };
interface DebtSupplierTabProps { interface DebtSupplierTabProps {
@@ -466,7 +473,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
footer: () => { footer: () => {
const value = supplier?.total.total_price; const value = supplier?.total.total_price;
return ( return (
<div className={`text-right ${value || 0 < 0 ? 'text-red-500' : ''}`}> <div
className={`text-right ${value && value < 0 ? 'text-red-500' : ''}`}
>
{formatCurrency(value || 0)} {formatCurrency(value || 0)}
</div> </div>
); );
@@ -488,7 +497,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
footer: () => { footer: () => {
const value = supplier?.total.payment_price; const value = supplier?.total.payment_price;
return ( return (
<div className={`text-right ${value || 0 < 0 ? 'text-red-500' : ''}`}> <div
className={`text-right ${value && value < 0 ? 'text-red-500' : ''}`}
>
{formatCurrency(value || 0)} {formatCurrency(value || 0)}
</div> </div>
); );
@@ -510,7 +521,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
footer: () => { footer: () => {
const value = supplier?.total.debt_price; const value = supplier?.total.debt_price;
return ( return (
<div className={`text-right ${value || 0 < 0 ? 'text-red-500' : ''}`}> <div
className={`text-right ${value && value < 0 ? 'text-red-500' : ''}`}
>
{formatCurrency(value || 0)} {formatCurrency(value || 0)}
</div> </div>
); );